JavaRush /Blog Java /Random-ES /Herencia múltiple en Java. Composición versus herencia
DSergey_Kh
Nivel 12

Herencia múltiple en Java. Composición versus herencia

Publicado en el grupo Random-ES

Herencia múltiple en Java

La herencia múltiple le permite crear una clase que hereda de múltiples superclases. A diferencia de otros lenguajes de programación orientados a objetos populares, como C++, Java no permite la herencia múltiple de clases. Java no admite la herencia de clases múltiples porque puede provocar el problema del diamante. Y en lugar de buscar formas de resolver este problema, existen mejores opciones para lograr el mismo resultado que la herencia múltiple.

problema de diamantes

Para comprender el problema del diamante más fácilmente, supongamos que Java admite la herencia múltiple. En este caso, podríamos tener una jerarquía de clases como se muestra en la imagen a continuación. Herencia múltiple en Java.  Composición versus herencia - 1Supongamos que la clase SuperClasses abstracta y en ella se declara algún método. Tanto clases concretas ClassAcomo ClassB.
package com.journaldev.inheritance;
public abstract class SuperClass {
	public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of A");
	}
	//ClassA own method
	public void methodA(){
	}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of B");
	}
	//ClassB specific method
	public void methodB(){
	}
}
Ahora supongamos que queremos implementarlo ClassCy heredarlo de ClassAy ClassB.
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
	public void test(){
		//calling super class method
		doSomething();
	}
}
Tenga en cuenta que el método test()llama al método de superclase doSomething(). Esto genera ambigüedad porque el compilador no sabe qué método de superclase ejecutar. Este es un diagrama de clases en forma de diamante llamado problema del diamante. Ésta es la razón principal por la que Java no admite la herencia múltiple. Tenga en cuenta que el problema anterior con herencia de clases múltiples solo puede ocurrir con tres clases que tengan al menos un método común.

Herencia de interfaz múltiple

En Java, la herencia múltiple no se admite en las clases, pero sí en las interfaces. Y una interfaz puede ampliar muchas otras interfaces. A continuación se muestra un ejemplo sencillo.
package com.journaldev.inheritance;
public interface InterfaceA {
	public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
	public void doSomething();
}
Tenga en cuenta que ambas interfaces declaran el mismo método. Ahora podemos crear una interfaz que extienda ambas interfaces, como se muestra en el siguiente ejemplo.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();
}
Esto funciona muy bien porque las interfaces sólo declaran métodos y la implementación se realizará en clases que heredan la interfaz. Por lo tanto, no hay forma de generar ambigüedad en la herencia de múltiples interfaces.
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
	@Override
	public void doSomething() {
		System.out.println("doSomething implementation of concrete class");
	}
	public static void main(String[] args) {
		InterfaceA objA = new InterfacesImpl();
		InterfaceB objB = new InterfacesImpl();
		InterfaceC objC = new InterfacesImpl();

		//all the method calls below are going to same concrete implementation
		objA.doSomething();
		objB.doSomething();
		objC.doSomething();
	}
}
Tenga en cuenta que siempre que anule cualquier método de superclase o implemente un método de interfaz, utilice la anotación @Override. ¿ Qué pasa si queremos usar una función methodA()de una clase ClassAy una función methodB()de una clase ClassBen una clase ClassC? La solución está en el uso de la composición. A continuación se muestra una versión de la clase ClassCque usa composición para definir tanto los métodos de la clase como el método doSomething()de uno de los objetos.
package com.journaldev.inheritance;
public class ClassC{
	ClassA objA = new ClassA();
	ClassB objB = new ClassB();
	public void test(){
		objA.doSomething();
	}
	public void methodA(){
		objA.methodA();
	}
	public void methodB(){
		objB.methodB();
	}
}

Composición versus herencia

Una de las mejores prácticas de programación Java es "aprobar la composición antes de la herencia". Exploraremos algunos de los aspectos que favorecen este enfoque.
  1. Digamos que tenemos una superclase y una clase que la extiende:

    package com.journaldev.inheritance;
    public class ClassC{
    	public void methodC(){
    	}
    }
    package com.journaldev.inheritance;
    public class ClassD extends ClassC{
    	public int test(){
    		return 0;
    	}
    }

    El código anterior se compila y funciona bien. Pero, ¿qué pasa si cambiamos la implementación de la clase ClassCcomo se muestra a continuación?

    package com.journaldev.inheritance;
    public class ClassC{
    	public void methodC(){
    	}
    	public void test(){
    	}
    }

    Tenga en cuenta que el método test()ya existe en la subclase, pero el tipo de retorno es diferente. Ahora la clase ClassDno se compilará y si usa algún IDE, le pedirá que cambie el tipo de retorno en la superclase o subclase.

    Ahora imagine una situación en la que tenemos una jerarquía de herencia de clases de varios niveles y no tenemos acceso a la superclase. No tendremos más remedio que cambiar la firma del método de nuestra subclase o su nombre para eliminar el error de compilación. También tendremos que cambiar el método de la subclase en todos los lugares donde se llama. Por tanto, la herencia hace que nuestro código sea frágil.

    El problema anterior nunca ocurrirá con la composición y esto la hace más atractiva para la herencia.

  2. Otro problema con la herencia es que exponemos todos los métodos de la superclase al cliente y si nuestra superclase no está diseñada adecuadamente y hay agujeros de seguridad, entonces aunque implementemos la mejor implementación de nuestra clase, nos vemos afectados por la mala implementación. de la superclase. La composición nos ayuda a proporcionar acceso controlado a los métodos de la superclase, mientras que la herencia no proporciona control sobre los métodos de la superclase. Este es también uno de los principales beneficios de la composición por herencia.

  3. Otro beneficio de la composición es que permite flexibilidad al llamar a los métodos. Nuestra implementación de la clase ClassCpresentada anteriormente no es óptima y garantiza que el tiempo de compilación esté vinculado al método que se llamará. Con cambios mínimos podemos hacer que la llamada al método sea flexible y dinámica.

    package com.journaldev.inheritance;
    public class ClassC{
    	SuperClass obj = null;
    	public ClassC(SuperClass o){
    		this.obj = o;
    	}
    	public void test(){
    		obj.doSomething();
    	}
    	public static void main(String args[]){
    		ClassC obj1 = new ClassC(new ClassA());
    		ClassC obj2 = new ClassC(new ClassB());
    
    		obj1.test();
    		obj2.test();
    	}
    }

    El resultado del programa presentado anteriormente:

    doSomething implementation of A
    doSomething implementation of B

    Esta flexibilidad en la llamada a métodos no está disponible con la herencia, lo que añade otro beneficio a la elección de composición.

  4. Las pruebas unitarias son más fáciles de realizar con composición porque sabemos que estamos usando todos los métodos de la superclase y podemos copiarlos para la prueba. Mientras que en herencia dependemos más de la superclase y no conocemos todos los métodos de la superclase que se utilizarán. Así que tenemos que probar todos los métodos de la superclase, lo cual supone un trabajo extra debido a la herencia.

    Idealmente, sólo deberíamos usar la herencia cuando la relación de subclase a superclase esté definida como "es". En todos los demás casos, se recomienda utilizar la composición.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION