Este artículo ilustra los conceptos de herencia y composición en Java. El primer ejemplo demuestra la herencia y luego muestra cómo mejorar el diseño de la herencia mediante la composición. Resumiremos cómo elegir entre ellos al final.
1. Herencia
Supongamos que tenemos una claseInsect
(insecto en inglés). Esta clase contiene dos métodos: 1. move()
(del movimiento en inglés) y 2. attack()
(del ataque en inglés)
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");
}
}
Ahora desea definir una clase Bee
(abeja inglesa), que es uno de los tipos Insect
, pero tiene diferentes implementaciones attack()
de y move()
. Esto se puede hacer usando herencia:
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();
}
}
El diagrama de jerarquía de clases es bastante simple: Resultado de la ejecución:
Fly
Fly
Attack
"Fly" se escribe dos veces, por lo que el método move()
se llama dos veces. Pero sólo se debe llamar una vez. El problema es causado por el super.attack()
. El método attack ()
llama a un método move()
de clase Insect
. Cuando una subclase llama super.attack ()
, también llama al método anulado move()
. Para solucionar el problema podemos:
- Elimina el
attack()
método de subclase. Esto hará que la subclase dependa de laattack()
implementación del método de la superclase. Siattack()attack()
la superclase comienza a utilizar un método diferente para moverse, la subclase también deberá cambiar. Esta es una mala encapsulación. -
Reescribe el método
attack()
de la siguiente manera:public void attack() { move(); System.out.println("Attack"); }
-
Esto garantiza el resultado correcto porque la subclase ya no depende de la superclase. Sin embargo, el código es un duplicado de la superclase. (el método
attack()
hace cosas más complejas que simplemente generar una cadena). Este no es un buen diseño de software y no debería haber código duplicado.
2. Composición
Puedes utilizar composición en lugar de herencia. Veamos una solución usándolo. La funciónattack()
se abstrae como una interfaz.
interface Attack {
public void move();
public void attack();
}
Se pueden definir diferentes tipos de ataques implementando la interfaz 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);
}
}
Dado que la función de ataque es externa, la clase Insect
ya no la contiene.
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;
}
}
Clase Bee
(del inglés Bee), cómo Insect
puede atacar un tipo.
// 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();
}
}
Diagrama de clase:
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();
}
}
Resultado de la ejecución:
fly
move
fly
sting
3. ¿Cuándo utilizar estos enfoques?
Los siguientes 2 puntos pueden ayudarle a decidir entre herencia y composición:- Si se trata de una relación entre clases de la forma "IS" y una clase quiere proporcionar todas sus interfaces a otra clase, entonces es preferible la herencia.
- si la relación es "TIENE", entonces se prefiere la composición.
- Bloch, Josué. Java efectivo. Pearson Educación India, 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
GO TO FULL VERSION