Questo articolo illustra i concetti di ereditarietà e composizione in Java. Il primo esempio illustra l'ereditarietà e quindi mostra come migliorare la progettazione dell'ereditarietà utilizzando la composizione. Alla fine riassumeremo come scegliere tra di loro.
1. Eredità
Ipotizziamo di avere una classeInsect
(inglese insetto) Questa classe contiene due metodi: 1. move()
(dall'inglese move) e 2. attack()
(dall'inglese 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");
}
}
Ora vuoi definire una classe Bee
(English bee), che è uno dei tipi Insect
, ma ha implementazioni diverse attack()
di e move()
. Questo può essere fatto usando l'ereditarietà:
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();
}
}
Il diagramma della gerarchia delle classi è abbastanza semplice: Risultato dell'esecuzione:
Fly
Fly
Attack
"Fly" viene digitato due volte, quindi il metodo move()
viene chiamato due volte. Ma dovrebbe essere chiamato solo una volta. Il problema è causato da super.attack()
. Il metodo attack ()
chiama un metodo move()
di classe Insect
. Quando una sottoclasse chiama super.attack ()
, chiama anche il metodo sovrascritto move()
. Per risolvere il problema possiamo:
- Eliminare il
attack()
metodo della sottoclasse. Ciò renderà la sottoclasse dipendenteattack()
dall'implementazione del metodo della superclasse. Seattack()attack()
la superclasse inizia a utilizzare un metodo diverso per lo spostamento, anche la sottoclasse dovrà cambiare. Questo è un cattivo incapsulamento. -
Riscrivi il metodo
attack()
come segue:public void attack() { move(); System.out.println("Attack"); }
-
Ciò garantisce il risultato corretto perché la sottoclasse non dipende più dalla superclasse. Tuttavia, il codice è un duplicato della superclasse. (il metodo
attack()
fa cose più complesse del semplice output di una stringa). Questa non è una buona progettazione del software e non dovrebbe esserci codice duplicato.
2. Composizione
È possibile utilizzare la composizione anziché l'ereditarietà. Diamo un'occhiata a una soluzione che lo utilizza. La funzioneattack()
è astratta come interfaccia.
interface Attack {
public void move();
public void attack();
}
È possibile definire diversi tipi di attacco implementando l'interfaccia 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);
}
}
Poiché la funzione di attacco è esterna, la classe Insect
non la contiene più.
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;
}
}
Classe Bee
(dall'inglese Bee), come un tipo Insect
può attaccare.
// 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();
}
}
Diagramma delle classi:
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();
}
}
Risultato dell'esecuzione:
fly
move
fly
sting
3. Quando utilizzare questi approcci?
I seguenti 2 punti possono aiutarti a decidere tra ereditarietà e composizione:- Se hai a che fare con una relazione tra classi della forma "IS" e una classe desidera fornire tutte le sue interfacce a un'altra classe, è preferibile l'ereditarietà.
- se la relazione è "HAS", è preferibile la composizione.
- Bloch, Giosuè. Java efficace. Pearson Education India, 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--wich-one-should...
GO TO FULL VERSION