JavaRush /Blog Java /Random-ES /Herencia vs composición en Java
dio
Nivel 16
Москва

Herencia vs composición en Java

Publicado en el grupo Random-ES
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. Herencia vs Composición en Java - 1

1. Herencia

Supongamos que tenemos una clase Insect(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: Herencia vs Composición en Java - 2Resultado 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:
  1. Elimina el attack()método de subclase. Esto hará que la subclase dependa de la attack()implementación del método de la superclase. Si attack()attack()la superclase comienza a utilizar un método diferente para moverse, la subclase también deberá cambiar. Esta es una mala encapsulación.
  2. Reescribe el método attack()de la siguiente manera:

    public void attack() {
    	move();
    	System.out.println("Attack");
    }
  3. 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.

Este diseño de herencia es malo porque la subclase depende de los detalles de implementación de su superclase. Si la superclase cambia, la subclase no funcionará correctamente.

2. Composición

Puedes utilizar composición en lugar de herencia. Veamos una solución usándolo. La función attack()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 Insectya 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 Insectpuede 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: Herencia vs Composición en Java - 3
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:
  1. 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.
  2. si la relación es "TIENE", entonces se prefiere la composición.
Por tanto, la herencia y la composición tienen sus propias aplicaciones y vale la pena comprender sus méritos. Enlaces:
  1. Bloch, Josué. Java efectivo. Pearson Educación India, 2008
  2. https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
Enlace al artículo original Traducido
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION