polimorfismo - implementacion de herencia multiple en java
Herencia múltiple de Java (16)
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 la clase Animal
esta tiene subclases de Bird
y Horse
y necesito hacer una clase de Pegasus
que se extienda desde Bird
y Horse
ya que Pegasus
es tanto un pájaro como un caballo.
Creo que este es el clásico problema de diamantes. Por lo que puedo entender, la forma clásica de resolver esto es hacer interfaces entre las clases de Animal
, Bird
y Horse
e implementar Pegasus
partir de ellas.
Me preguntaba si había otra manera 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, eso sería genial pero no necesario.
- Definir interfaces para definir las capacidades. Puede definir múltiples interfaces para múltiples capacidades. Estas capacidades pueden ser implementadas por Animal o Pájaro específico.
- Use herencia para establecer relaciones entre clases al compartir datos / métodos no estáticos y no públicos.
- 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 ejemplo a continuación para una mejor comprensión
¿Puedo sugerir el concepto de Duck-typing ?
Lo más probable es que tiendes a hacer que el Pegasus amplíe una interfaz de Aves y Caballos, pero el tipado de patos en realidad 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 Pegaso debería heredar una Flyable
Flyable y digamos una Gallopable
Gallopable.
Este tipo de concepto se utiliza en el patrón de estrategia . El ejemplo dado realmente muestra cómo un pato hereda el FlyBehaviour
y el QuackBehaviour
y aún puede haber patos, por ejemplo, el RubberDuck
, que no puede volar. También podrían haber hecho que el Duck
extendiera una clase de Bird
pero luego habrían renunciado a cierta flexibilidad, porque cada Duck
podría volar, incluso el pobre RubberDuck
.
Como ya sabrá, la herencia múltiple de clases en Java no es posible, pero es posible con las interfaces. También puede considerar usar el patrón de diseño de la composición.
Escribí un artículo muy completo sobre composición hace unos años ...
Creo que depende mucho de tus necesidades, y de cómo tus clases de animales deben usarse en tu código.
Si desea poder utilizar los métodos y características de sus implementaciones de Caballo y Ave dentro de su clase Pegasus, entonces podría implementar Pegasus como composition de un pájaro y un caballo:
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 Entity-Component-System lugar de herencia para definir sus animales. Por supuesto, esto significa que no tendrá clases de Java individuales de los animales, sino que solo estarán definidas por sus componentes.
Algunos pseudocódigos para un enfoque Entidad-Componente-Sistema 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);
}
Ehm, tu clase puede ser la subclase solo para 1 persona más, 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, puedes decir que el Pegaso es un pájaro, que puede caminar, y tiene 4 patas, todo depende de cómo es más fácil para ti escribir el código.
Al igual que 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
}
}
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 en 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.
Es seguro mantener un caballo en un establo con media puerta, ya que un caballo no puede llegar a 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 sobre la herencia múltiple, sin embargo, ahora que he estado programando durante más de 15 años, ya no me importa implementar una herencia múltiple.
La mayoría de las veces, cuando trato de lidiar con un diseño que apunta hacia la herencia múltiple, más tarde he llegado a publicar que había omitido entender el dominio del problema.
O
Hay dos enfoques fundamentales para combinar objetos juntos:
- El primero es Herencia . Como ya ha identificado, las limitaciones de la herencia significan que no puede hacer lo que necesita aquí.
- El segundo es 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, a continuación, agrega más objetos que dan las propiedades y comportamientos que necesita.
Por ejemplo:
- Pájaro extiende implementos animales IFlier
- Caballo se extiende Implementos de animales IHerbivore, IQuadruped
- Pegasus extiende Implementos de animales IHerbivore, IQuadruped, IFlier
Ahora IFlier
ve así:
interface IFlier {
Flier getFlier();
}
Así que Bird
ve así:
class Bird extends Animal implements IFlier {
Flier flier = new Flier();
public Flier getFlier() { return flier; }
}
Ahora tiene todas las ventajas de la herencia. Puede 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 tienes toda la flexibilidad de Composition. Puede aplicar tantas interfaces diferentes y clases de respaldo compuestas como desee para cada tipo de Animal
, con todo el control que necesite sobre cómo se configura cada bit.
Estrategia, patrón, alternativa, enfoque, a, composición
Un enfoque alternativo que depende de qué y cómo esté haciendo es que la clase base Animal
contenga una colección interna para mantener la lista de comportamientos diferentes. En ese caso, terminas usando algo más cercano al Patrón de Estrategia. Eso sí ofrece ventajas en términos de simplificación del código (por ejemplo, Horse
no necesita saber nada acerca de Quadruped
o Herbivore
), pero si no haces también el enfoque de interfaz, pierdes muchas de las ventajas del polimorfismo, etc.
Java no tiene un problema de herencia múltiple, ya que no tiene herencia múltiple. Esto es por diseño, para resolver el problema de la herencia múltiple real (El problema del diamante).
Existen diferentes estrategias para mitigar el problema. El más alcanzable 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 las tarjetas para el futuro de Java, pero lo dudo.
Si tu pregunta es académica, entonces la solución correcta es que Bird y Horse son más concretos, y es falso suponer que un Pegaso es simplemente un pájaro y un caballo combinados. Sería más correcto decir que un Pegaso tiene ciertas propiedades intrínsecas en común con los Pájaros y los Caballos (es decir, que quizás tengan ancestros comunes). Esto puede ser suficientemente modelado como lo señala la respuesta de Moritz.
Las interfaces no simulan herencia múltiple. Los creadores de Java consideraron 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 composición de objetos de uso único. 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 permítales delegar la llamada al controlador correspondiente.
Aquí las interfaces pueden ser útiles: si Component1
implementa la interfaz Interface1
y Component2
implementa Interface2
, puede definir
class Main implements Interface1, Interface2
Para que pueda usar objetos intercambiablemente donde el contexto lo permita.
Entonces, desde mi punto de vista, no puedes entrar en problemas con los diamantes.
Para reducir la complejidad y simplificar el idioma, la herencia múltiple no es compatible con 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 llaman desde un objeto de clase hijo, habrá ambigüedad para llamar al método de clase A o B.
Debido a 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, ahora habrá un error de tiempo de compilación.
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?
}
}
Para resolver el problema de la herencia mutiple en Java → se usa la interfaz
J2EE (core JAVA) Notas del Sr. KVR Página 51
Día - 27
- Las interfaces se usan básicamente para desarrollar tipos de datos definidos por el usuario.
- Con respecto a las interfaces, podemos lograr el concepto de herencias múltiples.
- Con las interfaces podemos lograr el concepto de polimorfismo, vinculación dinámica 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 la (s) interfaz (es) 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. ''Implements'' es una palabra clave que se utiliza para heredar las características de la (s) interfaz (es) 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; };
Podrías crear interfaces para clases de animales (clase en el sentido biológico), como public interface Equidae
para caballos y public interface Avialae
para aves (no soy biólogo, por lo que los términos pueden ser incorrectos).
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 parte 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 Brian comenta , esto es algo que el OP ya sabía.
Sin embargo, quiero enfatizar que sugiero evitar el problema de "herencia múltiple" con interfaces y que no recomiendo usar interfaces que representen ya un tipo concreto (como Bird) pero más un comportamiento (otros se refieren a pato-tipado, que también es bueno, pero quiero decir simplemente: la clase biológica de pájaros, Avialae). Tampoco recomiendo usar nombres de interfaz que comiencen con una "I" IBird
, como IBird
, que simplemente no dice nada sobre por qué necesita una interfaz. Esa es la diferencia a la pregunta: construir la jerarquía de herencia usando interfaces, usar clases abstractas cuando sea útil, implementar clases concretas donde sea necesario y usar la delegación si corresponde.
Técnicamente hablando, solo se puede extender una clase a la vez e implementar múltiples interfaces, pero cuando se trata de la ingeniería de software, prefiero sugerir una solución específica para un problema que generalmente no se puede responder. 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 el "animal" ni el uso de un objeto animal, sino solo animales concretos.
Tengo una idea estúpida
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();
}
}
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, extendiendo una interfaz específica:
public class Bird implements IBird {
}
public class Horse implements IHorse{
}
public class Pegasus implements IPegasus {
}