この記事では、Java における継承と合成の概念を説明します。最初の例では継承を示し、次に合成を使用して継承設計を改善する方法を示します。選び方については最後にまとめていきます。
1. 継承
Insect
クラス(英語の昆虫)があると仮定しましょう。このクラスには 2 つのメソッドが含まれています: 1. move()
(英語の動きから) と 2. attack()
(英語の攻撃から)
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("Move");
}
public void attack() {
move(); //assuming an insect needs to move before attacking
System.out.println("Attack");
}
}
ここで、クラスBee
(English bee) を定義したいとします。これは タイプの 1 つですが、と のInsect
実装は異なります。これは継承を使用して行うことができます。 attack()
move()
class Bee extends Insect {
public Bee(int size, String color) {
super(size, color);
}
public void move() {
System.out.println("Fly");
}
public void attack() {
move();
super.attack();
}
}
public class InheritanceVSComposition {
public static void main(String[] args) {
Insect i = new Bee(1, "red");
i.attack();
}
}
クラス階層図は非常に単純です。 実行結果:
Fly
Fly
Attack
「Fly」を 2 回入力するため、メソッドがmove()
2 回呼び出されます。ただし、呼び出すのは 1 回だけにしてください。この問題は が原因で発生しますsuper.attack()
。このメソッドはクラスメソッドattack ()
を呼び出します。サブクラスが呼び出すと、オーバーライドされたメソッドも呼び出されます。この問題を解決するには、次のことができます。 move()
Insect
super.attack ()
move()
- サブクラスメソッドを削除します
attack()
。これにより、サブクラスがattack()
スーパークラスのメソッド実装に依存するようになります。スーパークラスが別の移動方法を使用し始めた場合attack()attack()
、サブクラスも変更する必要があります。これは悪いカプセル化です。 -
メソッドを
attack()
次のように書き換えます。public void attack() { move(); System.out.println("Attack"); }
-
サブクラスがスーパークラスに依存しなくなるため、これにより正しい結果が保証されます。ただし、コードはスーパークラスの複製です。(このメソッドは、
attack()
単に文字列を出力するだけではなく、より複雑な処理を実行します)。これは適切なソフトウェア設計ではないため、重複したコードがあってはなりません。
2. 構成
継承の代わりに合成を使用することもできます。それを使った解決策を見てみましょう。関数はattack()
インターフェイスとして抽象化されます。
interface Attack {
public void move();
public void attack();
}
Attack インターフェイスを実装することで、さまざまなタイプの攻撃を定義できます。
class AttackImpl implements Attack {
private String move;
private String attack;
public AttackImpl(String move, String attack) {
this.move = move;
this.attack = attack;
}
@Override
public void move() {
System.out.println(move);
}
@Override
public void attack() {
move();
System.out.println(attack);
}
}
攻撃関数は外部であるため、クラスにはInsect
それが含まれなくなりました。
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
クラスBee
(English Bee より)、タイプがどのようにInsect
攻撃できるか。
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
private Attack attack;
public Bee(int size, String color, Attack attack) {
super(size, color);
this.attack = attack;
}
public void move() {
attack.move();
}
public void attack() {
attack.attack();
}
}
クラス図:
public class InheritanceVSComposition2 {
public static void main(String[] args) {
Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
a.attack();
// if you need another implementation of move()
// there is no need to change Insect, we can quickly use new method to attack
Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
b.attack();
}
}
実行結果:
fly
move
fly
sting
3. これらのアプローチをいつ使用するか?
次の 2 つのポイントは、継承と合成のどちらを選択するかを決定するのに役立ちます。- 「IS」形式のクラス間の関係を扱っていて、クラスがそのすべてのインターフェイスを別のクラスに提供したい場合は、継承が推奨されます。
- 関係が「HAS」の場合、合成が優先されます。
- ブロッホ、ジョシュア。効果的なJava。ピアソン教育インド、2008 年
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--どれをすべきか...
GO TO FULL VERSION