2010-02-10 10 views
23

Composición y herencia.¿Hay algo que la composición no pueda lograr que la herencia pueda?

Soy consciente de que ambas son herramientas para elegir cuando corresponda, y el contexto es muy importante al elegir entre composición y herencia. Sin embargo, la discusión sobre el contexto apropiado para cada uno suele ser un poco confusa; esto me hace comenzar a considerar cuán claramente la herencia y el polimorfismo son aspectos separados de la POO tradicional.

El polimorfismo permite especificar las relaciones "is-a" de la misma manera que la herencia. Particularmente, heredar de una clase base implícitamente crea una relación polimórfica entre esa clase y sus subclases. Sin embargo, mientras que el polimorfismo se puede implementar utilizando interfaces puras, la herencia complica la relación polimórfica transfiriendo simultáneamente los detalles de implementación. De esta manera, la herencia es bastante distinta del polimorfismo puro.

Como herramienta, la herencia sirve programadores de manera diferente que el polimorfismo (a través de interfaces de puros), simplificando la implementación reutilización en casos triviales. Sin embargo, en la mayoría de los casos, los detalles de implementación de una superclase entran en conflicto sutilmente con los requisitos de una subclase. Es por eso que tenemos "reemplazos" y "miembros que se esconden". En estos casos, la reutilización de implementación ofrecida por herencia se compra con el esfuerzo adicional de verificar los cambios de estado y las rutas de ejecución a través de niveles de código en cascada: los detalles completos de implementación de la subclase se reparten entre varias clases, lo que generalmente significa archivos múltiples, de los cuales solo porciones se aplican a la subclase en cuestión. Mirar a través de esa jerarquía es absolutamente necesario cuando se trata de herencia, ya que sin mirar el código de la superclase, no hay forma de saber qué detalles sin justificación están alterando su estado o desviando su ejecución.

En comparación, el uso exclusivo de la composición garantiza que verá qué estado puede ser modificado por objetos explícitamente instanciados cuyos métodos se invocan a su criterio. La implementación realmente plana aún no se logra (y en realidad ni siquiera es deseable, ya que el beneficio de la programación estructurada es la encapsulación y abstracción de los detalles de implementación) pero aún así obtienes la reutilización de código, y solo tendrás que mirar en un solo lugar cuando el código se comporta mal

Con el objetivo de poner a prueba estas ideas en la práctica, dejando de lado la herencia tradicional para una combinación de polimorfismo basado en interfaz pura y composición de objetos, me pregunto,

¿Existe composición de objetos nada y las interfaces no pueden lograr eso puede herencia ?

Editar

En las respuestas hasta ahora, ewernli cree que no hay proezas técnicas disponibles para una técnica pero no la otra; luego menciona cómo diferentes patrones y enfoques de diseño son inherentes a cada técnica. Esto es razonable. Sin embargo, la sugerencia me lleva a refinar mi pregunta al preguntar si el uso exclusivo de la composición y las interfaces en lugar de la herencia tradicional prohibiría el uso de cualquier patrón de diseño importante. Y si es así, ¿no hay patrones equivalentes para usar en mi situación?

+2

Personalmente, me gusta mezclar. :) –

+1

No tengo tiempo para verificar la duplicación efectiva, pero este tema de herencia v. Composición se visita periódicamente en SO, por ej. http://stackoverflow.com/questions/216523/object-oriented-best-practices-inheritance-v-composition-v-interfaces or http://stackoverflow.com/questions/1598722/should-i-use-inheritance -o-composición. "Uno siempre puede darle un giro a este tema y llamarlo una nueva visión de las cosas ..." Sin embargo, una cosa en la que deberíamos estar de acuerdo es que este tipo de preguntas no conducen a respuestas definitivas o incluso autorizadas. Tal vez un CW sea el formato más apropiado ... – mjv

+0

No quise repetir un debate cansado. Es cierto que la forma en que expuse mi caso fue más bien una muestra unilateral de dicho debate, pero mi principal interés es responder si hay o no un uso para la herencia que realmente no puede ser reemplazado por composición e interfaces. p.s., ¿qué es un formato CW? Tal vez lo intente ... –

Respuesta

21

Técnicamente, todo lo que se puede realizar con herencia se puede realizar también con la delegación. Entonces la respuesta sería "no".

La transformación de la herencia en la delegación

Supongamos que tenemos las siguientes clases implementadas con la herencia:

public class A { 
    String a = "A"; 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(this.getDisplayName() }; 
} 

public class B extends A { 
    String b = "B"; 
    void getDisplayName() { return a + " " + b; } 
    void doSomething() { super.doSomething() ; ... }  
} 

La materia trabaja muy bien, y llamando printName en una instancia de B imprimirá "A B" en el consola.

Ahora, si volvemos a escribir que con la delegación, obtenemos:

public class A { 
    String a = "A"; 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(this.getName() }; 
} 

public class B { 
    String b = "B"; 
    A delegate = new A(); 
    void getDisplayName() { return delegate.a + " " + b; } 
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... } 
} 

Tenemos que definir printName en B y también para crear el delegado cuando se crea una instancia B. Una llamada al doSomething funcionará de manera similar que con la herencia. Pero una llamada al printName imprimirá "A" en la consola. De hecho, con la delegación, hemos perdido el poderoso concepto de que "esto" está ligado a la instancia del objeto y los métodos de base pueden invocar a los métodos que deben anularse.

Esto se puede resolver en el idioma compatible con la delegación pura. Con la delegación pura, "esto" en el delegado seguirá haciendo referencia a la instancia de B. Lo que significa que this.getName() iniciará el envío del método de la clase B. Logramos lo mismo que con la herencia. Este es el mecanismo utilizado en el lenguaje prototype-based, como Self, que tiene delegación tiene una característica integrada (Puede leer here cómo funciona la herencia en sí mismo).

Pero Java no tiene delegación pura. ¿Cuándo está atascado? No, en serio, todavía podemos hacerlo nosotros mismos con un poco más de esfuerzo:

public class A implements AInterface { 
    String a = "A"; 
    AInterface owner; // replace "this" 
    A (AInterface o) { owner = o } 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(owner.getName() }; 
} 

public class B implements AInterface { 
    String b = "B"; 
    A delegate = new A(this); 
    void getDisplayName() { return delegate.a + " " + b; } 
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... } 
} 

Somos básicamente re-aplicación de lo que la herencia integrado proporciona. ¿Tiene sentido? No realmente. Pero ilustra que la herencia siempre se puede convertir en delegación.

Discusión

Inheritance se caracteriza por el hecho de que una clase base puede llamar a un método que se reemplaza en una clase sub. Esta es, por ejemplo, la esencia del template pattern. Tales cosas no se pueden hacer fácilmente con la delegación. Por otro lado, esto es exactamente lo que hace que la herencia sea difícil de usar. Se requiere un giro mental para comprender dónde ocurre el envío polimórfico y cuál es el efecto si los métodos se anulan.

Existen algunos escollos conocidos sobre la herencia y fragilidad que pueden introducirse en el diseño. Especialmente si la jerarquía de clases evoluciona. También puede haber algunos problemas con equality en hashCode y equals si se utiliza la herencia. Pero, por otro lado, sigue siendo una forma muy elegante de resolver algunos problemas.

Además, incluso si la herencia se puede sustituir por delegación, uno puede argumentar que todavía logran propósito diferente y se complementan entre sí - no transmiten la misma intención que no es capturado por pura técnica equivalencia.

(Mi teoría es que cuando alguien comienza a hacer OO, estamos tentados a usar en exceso la herencia porque se percibe como una característica del idioma.Luego aprendemos la delegación, que es un patrón/enfoque, y aprendemos a apreciarlo también. Después de un tiempo, encontramos un equilibrio entre ambos y desarrollamos el sentido de la intuición de cuál es mejor, en cuyo caso. Pues bien, como se puede ver, todavía me gustan los dos, y ambos merecen cierta cautela antes de ser introducido.)

Alguna literatura

herencia y la delegación son métodos alternativos para la definición y el intercambio incremental de . Tiene comúnmente se cree que la delegación proporciona un modelo más poderoso. Este documento demuestra que hay un modelo "natural" de herencia que captura todas las propiedades de la delegación . Independientemente, ciertas restricciones en la capacidad de la delegación para capturar la herencia son demostradas. Finalmente, se describe un nuevo marco que captura por completo la delegación y la herencia, y se exploran algunas de las ramificaciones de este modelo híbrido .

Uno de los más problemáticos-nociones más intrigantes y en el mismo tiempo en programación orientada a objetos es herencia. La herencia comúnmente es considerada como la característica que distingue la programación orientada a objetos de otros paradigmas de programación modernos, pero los investigadores rara vez acuerdan su significado y uso. [...]

Debido a la fuerte acoplamiento de las clases y la proliferación de miembros de la clase que no sean necesarios inducida por herencia, la sugerencia de utilizar la composición y la delegación en lugar tiene convertirse en un lugar común La presentación de una refactorización correspondiente en la literatura puede llevar a creer que tal transformación es una tarea directa. [...]

+0

Es parte de este extracto de un artículo publicado ? Si es así, estaría interesado en un enlace o doi. –

+2

Los primeros párrafos son míos y es mi opinión. Luego hay 3 referencias a trabajos relacionados con el tema para el cual he citado el resumen. El doi se puede encontrar en los enlaces que apuntan a portal.acm.org. Lo siento si eso no estaba claro. Déjame saber si no puedes encontrarlos como pdf. – ewernli

+0

Gracias, agradezco la aclaración. –

3

composición no puede arruinar la vida como lo hace la herencia, cuando los programadores de armas rápidas tratan de resolver los problemas mediante la adición de métodos y extender las jerarquías (en lugar de dar una idea de las jerarquías naturales)

composición no pueda resultado en diamantes extraños, que causan que los equipos de mantenimiento quemen el aceite de la noche rascándose la cabeza

La herencia fue la esencia de la discusión en los patrones de GOF Design que no habría sido lo mismo si los programadores usaran Composition en primer lugar.

+0

Marcarme como un troll, pero cometí todos estos errores antes de aprender de ellos – questzen

1

Considere la posibilidad de utilizar un kit de herramientas gui.

Un control de edición es una ventana, debe heredar las funciones de cerrar/habilitar/pintar de la ventana; no contiene una ventana.

Luego, un control de texto enriquecido debería contener las funciones de guardar y editar/leer/cortar/pegar de los controles que sería muy difícil de usar si solo contuviera una ventana y un control de edición.

+0

Cuando hablo de clases de GUI, creo que es demasiado fácil confundir la composición visual con la composición de clase. Se puede crear IWindow declarando firmas para objetos similares a ventanas, y Ventana para dibujar ventanas y manejar eventos. Luego cree IEditControl y EditControl, que implementan IWindow. EditControl simplemente delega sus responsabilidades IWindow en el objeto Window. Públicamente, EditControl se parece a cualquier otra ventana IW, y funciona como una ventana con adornos EditControl. Por último, RichTextControl implementa IEditControl, delegando a su propio objeto EditControl. El patrón es encadenable. –

+1

Es cierto que la mayoría de los ejemplos de herencia (clases de empleados, etc.) se realizan mejor como composición. Pero los juegos de herramientas GUI realmente se benefician de la herencia (IMHO) –

+2

Respetuosamente: Mi empresa mantiene un sistema legado masivo en .Net; Por el ejemplo de MSFT con WinForms, los desarrolladores originales usaron gran cantidad de herencia para implementar clases de IU. Tenemos 18 cuadros combinados únicos (aunque similares), 12 formularios base y una jerarquía de herencia de hasta 8 niveles de profundidad sobre la CLI. Nuestro marco de interfaz de usuario es un laberinto, tan inconsistente y frágil, que el equipo de desarrollo actual está aterrado ante el más mínimo cambio. Pero, a menudo, todo este código no satisface las nuevas necesidades ... La composición habría significado que pudiéramos escoger y elegir características según sea necesario, en lugar de infinitas * derivar * nuevas combinaciones. –

1

Puedo estar equivocado acerca de esto, pero lo voy a decir de todos modos, y si alguien tiene una razón por la que estoy equivocado, por favor solo responda con un comentario y no me vote. Hay una situación en la que puedo pensar donde la herencia sería superior a la composición.

Supongamos que tengo una biblioteca de widgets de fuente cerrada que estoy usando en un proyecto (lo que significa que los detalles de la implementación son un misterio para mí además de lo que está documentado). Ahora supongamos que cada widget tiene la capacidad de agregar widgets secundarios. Con la herencia, podría extender la clase Widget para crear un CustomWidget, y luego agregar CustomWidget como widget infantil de cualquier otro widget en la biblioteca. Entonces mi código para añadir un CustomWidget sería algo como esto:

Widget baseWidget = new Widget(); 
CustomWidget customWidget = new CustomWidget(); 
baseWidget.addChildWidget(customWidget); 

muy limpia, y se mantiene en línea con las convenciones de la biblioteca para añadir widgets hijos. Sin embargo, la composición, que tendría que ser algo como esto:

Widget baseWidget = new Widget(); 
CustomWidget customWidget = new CustomWidget(); 
customWidget.addAsChildToWidget(baseWidget); 

No es tan limpio, y también rompe las convenciones de la biblioteca

Ahora no estoy diciendo que no podía lograr esto con la composición (de hecho, mi ejemplo muestra que puedes hacerlo muy claramente), simplemente no es ideal en todas las circunstancias y puede llevar a romper convenciones y otras soluciones visuales menos atractivas visualmente.

+2

Usted enfatizó que Widget estaba en una biblioteca externa de código cerrado. Si Widget.addChildWidget() tomó un argumento de tipo Widget, entonces la herencia de IMO es la única opción viable. La biblioteca tiene padres que realizan el seguimiento de los niños, pero su ejemplo de composición requiere CustomWidgets para rastrear a sus padres en lugar de ser rastreados por ellos. (¡Yuck!) Si Widget.addChildWidget() tomó un argumento de tipo IWidget, la composición aún podría funcionar, ya que CustomWidget podría implementar IWidget, al tiempo que posponía algunos comportamientos a un Widget privado. por cierto, el poder real de Interface Composition viene con IOC & DI. –

-1

Sí. Es la identificación del tipo de tiempo de ejecución (RTTI).

+0

Si necesita RTTI, francamente, está violando DIP. Pero meh. Haga que una interfaz declare un 'getType()' o una función similar, e implemente la interfaz. Ahora tiene su propia definición de "tipo", que puede moldear en cualquier cosa que quiera ... y las clases involucradas no necesitan relacionarse entre sí. – cHao

Cuestiones relacionadas