抽象クラスの使いどころと聞いて何を思い浮かべるでしょうか?
インターフェイスを使っていると、抽象クラスの存在は霞んでしまいがちです
しかし、明確なメリットもあります
それは...
- 動的なフィールドを持てる事
インターフェースには無い明確な個性です
何故なら、抽象クラスに対しインターフェイスは静的なインスタンスであるため、動的フィールドを自身で運用できないためです
抽象クラスは、これを高度に隠ぺいします
コード一覧
public abstract class Status{
private int hp,atk;
public Status(int hp,int atk){
this.hp=hp;
this.atk=atk;
}
protected void damage(Status target){
int currentHP=0;
target.hp=(currentHP=target.hp-this.atk)>=0?currentHP:0;
}
public void currentHP(){
System.out.println(this+":"+this.hp);
}
}
public abstract class Fighter<T extends Status> extends Status{
public Fighter(int hp,int atk){
super(hp,atk);
}
public void attack(T target){
super.damage(target);
}
}
public class Player extends Fighter<Enemy>{
public Player(int hp,int atk){
super(hp,atk);
}
}
public class Enemy extends Fighter<Player>{
public Enemy(int hp,int atk){
super(hp,atk);
}
}
public class Main{
public static void main(String[] args){
Player player=new Player(100,20);
Enemy enemy=new Enemy(100,10);
for(int i=10;i>0;--i)
player.attack(enemy);
enemy.currentHP();
}
}
Enemy@27716f4:0
基本的なステータスを管理する抽象クラスとしてStatus
を定義します
これはクラスなので、プライベートなフィールドを持つことが出来ます
受け取ったインスタンスがStatus
型であれば自身のフィールドへアクセスできるので、これを操作するメソッドを定義できます
ここではdamage
メソッドで操作します
public abstract class Status{
private int hp,atk;
public Status(int hp,int atk){
this.hp=hp;
this.atk=atk;
}
protected void damage(Status target){
int currentHP=0;
target.hp=(currentHP=target.hp-this.atk)>=0?currentHP:0;
}
public void currentHP(){
System.out.println(this+":"+this.hp);
}
}
damage
メソッドはprotected
なため、子クラスで呼び出すことが出来ます
しかしStatus
と継承関係にないクラスからは呼び出せません
これによりインスタンスのパラメータに外部からはアクセスできないよう制限を設けることができます
public abstract class Fighter<T extends Status> extends Status{
public Fighter(int hp,int atk){
super(hp,atk);
}
public void attack(T target){
super.damage(target);
}
}
attack
メソッドには型引数T
が定義されています
子クラスとなる以下のPlayer
とEnemy
でこの型引数にクラスを指定することで、引数の型を各インスタンスごとに変更することが出来ます
public class Player extends Fighter<Enemy>{
public Player(int hp,int atk){
super(hp,atk);
}
}
public class Enemy extends Fighter<Player>{
public Enemy(int hp,int atk){
super(hp,atk);
}
}
例えば現在Player
クラスから呼び出し可能なattack
メソッドは、親クラスであるFighter
の型引数にEnemy
を指定しているため、Enemy
型以外のインスタンスを受け付けません
そのためこのメソッドにPlayer
のインスタンスを代入すると構文エラーを報告します
public class Main{
public static void main(String[] args){
Player player=new Player(100,20);
Enemy enemy=new Enemy(100,10);
for(int i=10;i>0;--i)
player.attack(player);
enemy.currentHP();
}
}
Main.java:7: エラー: 不適合な型: PlayerをEnemyに変換できません:
player.attack(player);
このように、抽象クラスを用いることでパラメータがより厳格に保護されます
インスタンスが動的であることは、それ自体が一つの利点であると言えます
抽象クラスはそれ単体のインスタンス化を禁止したい場面でも役立ちます
今回のようにメソッドの適用対象を限定したいケースでは、あるクラスを継承することが確実に保証される手段として有用です