Herencia múltiple de Java


168

En un intento por comprender completamente cómo resolver los problemas de herencia múltiple de Java, tengo una pregunta clásica que necesito aclarar.

Digamos que tengo clase, Animalesto tiene subclases Birdy Horsenecesito hacer una clase Pegasusque se extienda desde Birdy Horseya Pegasussea ​​un pájaro y un caballo.

Creo que este es el clásico problema del diamante. Por lo que puedo entender la forma clásica de resolver esto es para que el Animal, Birdy Horselas interfaces de clases e implementar Pegasusde ellos.

Me preguntaba si había otra forma de resolver el problema en el que todavía puedo crear objetos para pájaros y caballos. Si hubiera una manera de poder crear animales también sería genial, pero no necesario.


66
Creo que puede crear manualmente las clases y almacenarlas como miembros (composición en lugar de herencia). Con la clase Proxy ( docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html ) esta podría ser una opción, aunque también necesitará las interfaces.
Gábor Bakos

44
@RAM, entonces no debería extender Bird, sino tener un comportamiento que le permita tomar un vuelo. : D problema resuelto
Yogesh

11
Exactamente. ¿Qué tal una interfaz CanFly? :-)
Sorprendido coco

28
Creo que este es el enfoque equivocado. Tienes animales: caballos, pájaros. Y tienes propiedades: volar, carnívoro. Un Pegasus no es un HorseBird, es un caballo que puede volar. Las propiedades deberían en las interfaces. Por lo tanto public class Pegasus extends Horse implements Flying.
Boris the Spider

12
Entiendo por qué piensas que está mal y no se adhiere a las reglas de la biología y aprecio tu preocupación, pero en lo que respecta al programa que necesito construir, que realmente tiene que ver con los bancos, este fue el mejor enfoque para mí. Como no quería publicar mis problemas reales, ya que eso estaría en contra de las reglas, cambié un poco el ejemplo. Gracias aunque ...
Sheli

Respuestas:


115

Podría crear interfaces para clases de animales (clase en el significado biológico), como public interface Equidaepara caballos y public interface Avialaepájaros (no soy biólogo, por lo que los términos pueden estar equivocados).

Entonces aún puedes crear un

public class Bird implements Avialae {
}

y

public class Horse implements Equidae {}

y también

public class Pegasus implements Avialae, Equidae {}

Agregando de los comentarios:

Para reducir el código duplicado, puede crear una clase abstracta que contenga la mayoría del código común de los animales que desea implementar.

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

Actualizar

Me gustaría agregar un detalle más. Como comenta Brian , esto es algo que el OP ya sabía.

Sin embargo, quiero enfatizar que sugiero evitar el problema de "herencia múltiple" con las interfaces y que no recomiendo usar interfaces que ya representen un tipo concreto (como Bird), sino más bien un comportamiento (otros se refieren a tipificación de patos, que también es bueno, pero quiero decir simplemente: la clase biológica de las aves, Avialae). Tampoco recomiendo usar nombres de interfaz que comiencen con una 'I' mayúscula, como IBird, que simplemente no dice nada sobre por qué necesita una interfaz. Esa es la diferencia con la pregunta: construya la jerarquía de herencia usando interfaces, use clases abstractas cuando sea útil, implemente clases concretas donde sea necesario y use la delegación si corresponde.


9
Lo cual ... es exactamente lo que el OP dice que saben que puedes hacer en la Q.
Brian Roach

44
Como Pegasus ya es un caballo (que vuela), creo que podría reutilizar más código si extiende el caballo e implementa Avialae.
Pablo Lozano

8
No estoy seguro, todavía no he visto un Pegaso. Sin embargo, en ese caso preferiría usar un AbstractHorse, que también se puede usar para construir cebras u otros animales similares a caballos.
Moritz Petersen

55
@MoritzPetersen Si realmente quiere volver a utilizar las abstracciones y dar nombres significativos, probablemente AbstractEquidaesería más adecuado que AbstractHorse. Sería extraño que una cebra extendiera un caballo abstracto. Buena respuesta, por cierto.
afsantos

3
¿La implementación de la escritura de pato también implicaría Duckla implementación de una clase Avialae?
mgarciaisaia

88

Hay dos enfoques fundamentales para combinar objetos juntos:

  • El primero es la herencia . Como ya ha identificado, las limitaciones de la herencia significan que no puede hacer lo que necesita aquí.
  • El segundo es la composición . Como la herencia ha fallado, debes usar la composición.

La forma en que esto funciona es que tienes un objeto Animal. Dentro de ese objeto, agrega otros objetos que le dan las propiedades y comportamientos que necesita.

Por ejemplo:

  • Bird extiende implementos animales IFlier
  • Caballo extiende implementos de animales IHerbivore, IQuadruped
  • Pegasus extiende implementos animales IHerbivore, IQuadruped, IFlier

Ahora IFliersolo se ve así:

 interface IFlier {
     Flier getFlier();
 }

Así se Birdve así:

 class Bird extends Animal implements IFlier {
      Flier flier = new Flier();
      public Flier getFlier() { return flier; }
 }

Ahora tiene todas las ventajas de la herencia. Puedes reutilizar el código. Puede tener una colección de IFliers, y puede usar todas las otras ventajas del polimorfismo, etc.

Sin embargo, también tiene toda la flexibilidad de Composición. Puede aplicar tantas interfaces diferentes y clases de respaldo compuesto como desee a cada tipo de Animal, con el control que necesite sobre cómo se configura cada bit.

Patrón de estrategia enfoque alternativo a la composición

Un enfoque alternativo que depende de qué y cómo esté haciendo es que la Animalclase base contenga una colección interna para mantener la lista de diferentes comportamientos. En ese caso, terminas usando algo más cercano al Patrón de Estrategia. Eso ventajas dan en términos de simplificación del código (por ejemplo, Horseno necesita saber nada sobre Quadrupedo Herbivore), pero si no lo hace también el enfoque de interfaz se pierde mucho de las ventajas del polimorfismo, etc.


Una alternativa similar también podría ser usar clases de tipos, aunque no son tan naturales en Java (debe usar métodos de conversión, etc.), esta introducción podría ser útil para tener la idea: typeclassopedia.bitbucket.org
Gábor Bakos

1
Esta es una solución mucho mejor que el enfoque recomendado en la respuesta aceptada. Por ejemplo, si luego quiero agregar "color de pico" a la interfaz de Bird, tengo un problema. Pegasus es un compuesto, que toma elementos de caballos y pájaros, pero no completamente caballo ni pájaro. Usar composición tiene mucho sentido en este escenario.
JDB todavía recuerda a Mónica

Pero getFlier()tiene que ser reimplementado para cada tipo de ave.
SMUsamaShah

1
@ LifeH2O Dividirlos en bloques de funcionalidad compartida y luego darles eso. es decir, que pueda tener MathsTeachery EnglishTeachertanto heredar Teacher, ChemicalEngineer, MaterialsEngineeretc hereda Engineer. Teachery Engineerambos implementan Component. La Personcontinuación, sólo tiene una lista de Components, y se les puede dar la derecha Components para que Person. es decir person.getComponent(Teacher.class), person.getComponent(MathsTeacher.class)etc.
Tim B

1
Esta es la mejor respuesta. Como regla general, la herencia representa "es" y una interfaz representa "puede". Un Pegaso ES un animal que puede volar y caminar. Un pájaro es un animal que puede volar. Un caballo es un animal que puede caminar.
Robear

43

Tengo una estúpida idea:

public class Pegasus {
    private Horse horseFeatures; 
    private Bird birdFeatures; 

   public Pegasus(Horse horse, Bird bird) {
     this.horseFeatures = horse;
     this.birdFeatures = bird;
   }

  public void jump() {
    horseFeatures.jump();
  }

  public void fly() {
    birdFeatures.fly();
  }
}

24
Funcionará, pero no me gusta ese tipo de enfoque (Wrappper) porque entonces parece que Pegaso TIENE un caballo, en cambio ES un caballo.
Pablo Lozano

Gracias. No pude evitar publicar la idea. Sé que es estúpido, pero no vi POR QUÉ es estúpido ...
Pavel Janicek

3
Es casi como una versión no refinada del enfoque de composición de la respuesta de Tim B.
Stephan

1
Esta es más o menos la forma aceptada de hacerlo, aunque también debería tener algo como IJumps con un método de "salto" implementado por Horse and Pegasus e IFlies con un método de "vuelo" implementado por Bird y Pegasus.
MatsT

2
@Pablo no, un Pegaso TIENE características de caballo y características de pájaro. +1 para la respuesta porque mantiene el código simple, a diferencia de las soluciones Java más clunkier, de generación de clases y adecuadas.
JaneGoodall

25

¿Puedo sugerir el concepto de mecanografía de pato ?

Lo más probable es que tengas la tendencia de hacer que el Pegasus extienda una interfaz de Bird y Horse, pero la escritura de pato realmente sugiere que debes heredar el comportamiento . Como ya se dijo en los comentarios, un pegaso no es un pájaro pero puede volar. Entonces su Pegasus debería heredar una Flyableinterfaz y digamos una Gallopableinterfaz.

Este tipo de concepto se utiliza en el Patrón de estrategia . El ejemplo dado realmente muestra cómo un pato hereda el FlyBehavioury QuackBehaviouraún puede haber patos, por ejemplo RubberDuck, que no pueden volar. También podrían haber hecho que la clase se Duckextendiera, Birdpero habrían renunciado a cierta flexibilidad, porque todos Duckpodrían volar, incluso los pobres RubberDuck.


19

Técnicamente hablando, solo puede extender una clase a la vez e implementar múltiples interfaces, pero al poner manos a la obra en ingeniería de software, preferiría sugerir una solución específica del problema que generalmente no responde. Por cierto, es una buena práctica de OO, no extender clases concretas / solo extender clases abstractas para evitar comportamientos de herencia no deseados; no existe tal cosa como un "animal" y no se usa un objeto animal sino solo animales concretos.


13

En Java 8, que aún se encuentra en la fase de desarrollo a partir de febrero de 2014, podría usar métodos predeterminados para lograr una especie de herencia múltiple de C ++. También puede echar un vistazo a este tutorial que muestra algunos ejemplos con los que debería ser más fácil comenzar a trabajar que la documentación oficial.


1
Sin embargo, tenga en cuenta que si su Bird y Horse tienen un método predeterminado, aún se encontrará con el problema del diamante y tendrá que implementarlo por separado en su clase Pegasus (u obtendrá un error del compilador).
Mikkel Løkke

@Mikkel Løkke: la clase Pegasus tiene que definir anulaciones para los métodos ambiguos, pero puede implementarlos simplemente delegando a cualquiera de los supermétodos (o ambos en un orden elegido).
Holger

12

Es seguro mantener un caballo en un establo con media puerta, ya que un caballo no puede pasar más de media puerta. Por lo tanto, configuro un servicio de alojamiento de caballos que acepta cualquier artículo de tipo caballo y lo coloca en un establo con media puerta.

Entonces, ¿es un caballo como un animal que puede volar incluso un caballo?

Solía ​​pensar mucho en la herencia múltiple, sin embargo, ahora que he estado programando durante más de 15 años, ya no me importa implementar la herencia múltiple.

En la mayoría de los casos, cuando he intentado hacer frente a un diseño que apuntaba hacia la herencia múltiple, más tarde he llegado a la conclusión de que no entendí el dominio del problema.

O

Si parece un pato y grazna como un pato pero necesita baterías, probablemente tenga la abstracción incorrecta .


Si leo su analogía correctamente, está diciendo que al principio las interfaces parecen geniales, ya que le permiten solucionar problemas de diseño, por ejemplo, puede usar la API de otra persona forzando sus clases a través de sus interfaces. Pero después de algunos años, se dio cuenta de que, para empezar, el problema era un mal diseño.
CS

8

Java no tiene un problema de herencia múltiple, ya que no tiene herencia múltiple. Esto es por diseño, para resolver el verdadero problema de herencia múltiple (El problema del diamante).

Existen diferentes estrategias para mitigar el problema. El más inmediato posible es el objeto Compuesto que Pavel sugiere (esencialmente cómo lo maneja C ++). No sé si la herencia múltiple a través de la linealización C3 (o similar) está en juego para el futuro de Java, pero lo dudo.

Si su pregunta es académica, entonces la solución correcta es que Bird y Horse son más concretos, y es falso suponer que un Pegasus es simplemente un Bird y un Horse combinados. Sería más correcto decir que un Pegaso tiene ciertas propiedades intrínsecas en común con las aves y los caballos (es decir, tienen ancestros comunes). Esto se puede modelar lo suficiente como señala la respuesta de Moritz.


6

Creo que depende mucho de sus necesidades y de cómo se usarán sus clases de animales en su código.

Si desea poder utilizar métodos y características de sus implementaciones de Horse and Bird dentro de su clase Pegasus, entonces podría implementar Pegasus como una composición de Bird y Horse:

public class Animals {

    public interface Animal{
        public int getNumberOfLegs();
        public boolean canFly();
        public boolean canBeRidden();
    }

    public interface Bird extends Animal{
        public void doSomeBirdThing();
    }
    public interface Horse extends Animal{
        public void doSomeHorseThing();
    }
    public interface Pegasus extends Bird,Horse{

    }

    public abstract class AnimalImpl implements Animal{
        private final int numberOfLegs;

        public AnimalImpl(int numberOfLegs) {
            super();
            this.numberOfLegs = numberOfLegs;
        }

        @Override
        public int getNumberOfLegs() {
            return numberOfLegs;
        }
    }

    public class BirdImpl extends AnimalImpl implements Bird{

        public BirdImpl() {
            super(2);
        }

        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return false;
        }

        @Override
        public void doSomeBirdThing() {
            System.out.println("doing some bird thing...");
        }

    }

    public class HorseImpl extends AnimalImpl implements Horse{

        public HorseImpl() {
            super(4);
        }

        @Override
        public boolean canFly() {
            return false;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }

        @Override
        public void doSomeHorseThing() {
            System.out.println("doing some horse thing...");
        }

    }

    public class PegasusImpl implements Pegasus{

        private final Horse horse = new HorseImpl();
        private final Bird bird = new BirdImpl();


        @Override
        public void doSomeBirdThing() {
            bird.doSomeBirdThing();
        }

        @Override
        public int getNumberOfLegs() {
            return horse.getNumberOfLegs();
        }

        @Override
        public void doSomeHorseThing() {
            horse.doSomeHorseThing();
        }


        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }
    }
}

Otra posibilidad es utilizar un enfoque de Entidad-Componente-Sistema en lugar de herencia para definir sus animales. Por supuesto, esto significa que no tendrá clases Java individuales de los animales, sino que solo se definirán por sus componentes.

Algunos pseudocódigos para un enfoque Entity-Component-System podrían verse así:

public void createHorse(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 4);
    entity.setComponent(CAN_FLY, false);
    entity.setComponent(CAN_BE_RIDDEN, true);
    entity.setComponent(SOME_HORSE_FUNCTIONALITY, new HorseFunction());
}

public void createBird(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 2);
    entity.setComponent(CAN_FLY, true);
    entity.setComponent(CAN_BE_RIDDEN, false);
    entity.setComponent(SOME_BIRD_FUNCTIONALITY, new BirdFunction());
}

public void createPegasus(Entity entity){
    createHorse(entity);
    createBird(entity);
    entity.setComponent(CAN_BE_RIDDEN, true);
}

4

puede tener una jerarquía de interfaz y luego extender sus clases desde las interfaces seleccionadas:

public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

y luego defina sus clases según sea necesario, ampliando una interfaz específica:

public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}

1
O puede simplemente: Clase pública Pegasus extiende Animal Implements Horse, Bird
Batman

OP ya está al tanto de esta solución, está buscando una forma alternativa de hacerlo
Yogesh

@Batman, por supuesto que puede, pero si quiere extender la jerarquía, necesitaría seguir este enfoque
richardtz

IBirdy IHorsedebería implementar en IAnimallugar deAnimal
oliholz

@Yogesh, tienes razón. Pasé por alto el lugar donde lo dice. Como "novato", ¿qué debo hacer ahora, eliminar la respuesta o dejarla allí ?, gracias.
richardtz

4

Ehm, tu clase puede ser la subclase de solo 1, pero aún así, puedes tener tantas interfaces implementadas como desees.

Un Pegaso es, de hecho, un caballo (es un caso especial de un caballo), que puede volar (que es la "habilidad" de este caballo especial). Por otro lado, puede decir que el Pegaso es un pájaro que puede caminar y tiene 4 patas; todo depende de cómo le sea más fácil escribir el código.

Como en tu caso puedes decir:

abstract class Animal {
   private Integer hp = 0; 
   public void eat() { 
      hp++; 
   }
}
interface AirCompatible { 
   public void fly(); 
}
class Bird extends Animal implements AirCompatible { 
   @Override
   public void fly() {  
       //Do something useful
   }
} 
class Horse extends Animal {
   @Override
   public void eat() { 
      hp+=2; 
   }

}
class Pegasus extends Horse implements AirCompatible {
   //now every time when your Pegasus eats, will receive +2 hp  
   @Override
   public void fly() {  
       //Do something useful
   }
}

3

Las interfaces no simulan herencia múltiple. Los creadores de Java consideran que la herencia múltiple es incorrecta, por lo que no existe tal cosa en Java.

Si desea combinar la funcionalidad de dos clases en una, use la composición de objetos. Es decir

public class Main {
    private Component1 component1 = new Component1();    
    private Component2 component2 = new Component2();
}

Y si desea exponer ciertos métodos, defínalos y permita que deleguen la llamada al controlador correspondiente.

Aquí las interfaces pueden ser útiles: si Component1implementa la interfaz Interface1y los Component2implementos Interface2, puede definir

class Main implements Interface1, Interface2

Para que pueda usar objetos indistintamente donde el contexto lo permita.

Entonces, en mi punto de vista, no puedes meterte en el problema del diamante.


No está mal de la misma manera que los punteros de memoria directa, los tipos sin signo y la sobrecarga del operador no están mal; simplemente no es necesario hacer el trabajo. Java fue diseñado como un lenguaje sencillo y fácil de aprender. No invente cosas y espere lo mejor, este es un campo de conocimiento, no de conjeturas.
Gimby


3
  1. Definir interfaces para definir las capacidades. Puede definir múltiples interfaces para múltiples capacidades. Estas capacidades pueden ser implementadas por animales o aves específicos .
  2. Utilizar herencia para establecer relaciones entre clases compartiendo datos / métodos no estáticos y no públicos.
  3. Use Decorator_pattern para agregar capacidades dinámicamente. Esto le permitirá reducir el número de clases y combinaciones de herencia.

Eche un vistazo al siguiente ejemplo para comprender mejor

¿Cuándo usar el patrón decorador?


2

Para reducir la complejidad y simplificar el lenguaje, no se admite la herencia múltiple en Java.

Considere un escenario donde A, B y C son tres clases. La clase C hereda las clases A y B. Si las clases A y B tienen el mismo método y lo llama desde un objeto de clase hijo, habrá ambigüedad para llamar al método de clase A o B.

Dado que los errores de tiempo de compilación son mejores que los errores de tiempo de ejecución, Java genera un error de tiempo de compilación si hereda 2 clases. Entonces, ya sea que tenga el mismo método o diferente, habrá un error de tiempo de compilación ahora.

class A {  
    void msg() {
        System.out.println("From A");
    }  
}

class B {  
    void msg() {
        System.out.println("From B");
    }  
}

class C extends A,B { // suppose if this was possible
    public static void main(String[] args) {  
        C obj = new C();  
        obj.msg(); // which msg() method would be invoked?  
    }
} 

2

Para resolver el problema de la herencia múltiple en Java → se utiliza la interfaz

J2EE (core JAVA) Notas del Sr. KVR Página 51

Día - 27

  1. Las interfaces se utilizan básicamente para desarrollar tipos de datos definidos por el usuario.
  2. Con respecto a las interfaces podemos lograr el concepto de herencias múltiples.
  3. Con las interfaces podemos lograr el concepto de polimorfismo, enlace dinámico y, por lo tanto, podemos mejorar el rendimiento de un programa JAVA en turnos de espacio de memoria y tiempo de ejecución.

Una interfaz es una construcción que contiene la colección de métodos puramente indefinidos o una interfaz es una colección de métodos puramente abstractos.

[...]

Día 28:

Sintaxis-1 para reutilizar las características de las interfaces a la clase:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
    variable declaration;
    method definition or declaration;
};

En la sintaxis anterior, clsname representa el nombre de la clase que hereda las características de 'n' número de interfaces. 'Implementos' es una palabra clave que se utiliza para heredar las características de las interfaces a una clase derivada.

[...]

Sintaxis-2 heredando 'n' número de interfaces a otra interfaz:

interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{     
    variable declaration cum initialization;
    method declaration;
};

[...]

Sintaxis-3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
  variable declaration;
  method definition or declaration;
};
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.