¿De dónde viene este concepto de "composición del favor sobre la herencia"?

143

En los últimos meses, el mantra "favorecer la composición sobre la herencia" parece haber surgido de la nada y convertirse casi en una especie de meme dentro de la comunidad de programación. Y cada vez que lo veo, estoy un poco desconcertado. Es como si alguien dijera "taladros a favor sobre martillos". En mi experiencia, la composición y la herencia son dos herramientas diferentes con diferentes casos de uso, y tratarlas como si fueran intercambiables y una fuera inherentemente superior a la otra no tiene sentido.

Además, nunca veo una explicación real de por qué la herencia es mala y la composición es buena, lo que me hace sospechar más. ¿Se supone que solo debe ser aceptado por fe? La sustitución de Liskov y el polimorfismo tienen beneficios bien conocidos y claros, y la OMI comprende todo el punto de usar la programación orientada a objetos, y nadie explica por qué deberían descartarse a favor de la composición.

¿Alguien sabe de dónde viene este concepto y cuál es la razón de ser?

Mason Wheeler
fuente
45
Como otros han señalado, ha existido desde hace mucho tiempo, me sorprende que acabes de escucharlo. Es intuitivo para cualquiera que haya estado construyendo grandes sistemas en lenguajes como Java por cualquier cantidad de tiempo. Es fundamental para cualquier entrevista que doy y cuando un candidato comienza a hablar sobre herencia, empiezo a dudar de su nivel de habilidad y cantidad de experiencia. Aquí hay una buena introducción de por qué la herencia es una solución frágil (hay muchas otras): artima.com/lejava/articles/designprinciples4.html
Janx
66
@ Janx: Tal vez eso es todo. No construyo grandes sistemas en lenguajes como Java; Los construyo en Delphi, y sin la sustitución y el polimorfismo de Liskov nunca podríamos hacer nada. Su modelo de objetos es diferente en ciertas formas que el de Java o C ++, y muchos de los problemas que esta máxima parece estar destinada a resolver no existen realmente, o son mucho menos problemáticos, en Delphi. Diferentes perspectivas desde diferentes puntos de vista, supongo.
Mason Wheeler
14
Pasé varios años en un equipo construyendo sistemas relativamente grandes en Delphi y los altos árboles de herencia ciertamente mordieron a nuestro equipo y nos causaron un dolor significativo. Sospecho que su atención a los principios SÓLIDOS lo está ayudando a evitar las áreas problemáticas, no su uso de Delphi.
Bevan
8
¿¡¿Los últimos meses?!?
Jas
66
En mi humilde opinión, ese concepto nunca se ha ajustado completamente a la variedad de lenguajes que admiten tanto la herencia de interfaz (es decir, subtipar con interfaces puras) como la herencia de implementación. Demasiadas personas siguen este mantra y no usan suficientes interfaces.
Uri

Respuestas:

136

Aunque creo que he escuchado discusiones de composición vs herencia mucho antes de GoF, no puedo señalar una fuente específica. Podría haber sido Booch de todos modos.

<rant>

Ah, pero como muchos mantras, este se ha degenerado en líneas típicas:

  1. Se presenta con una explicación y argumento detallados por una fuente respetada que acuña la frase clave como un recordatorio de la compleja discusión original.
  2. se comparte con un guiño conocedor de parte del club por unos pocos informados por un tiempo, generalmente al comentar errores n00b
  3. pronto, miles y miles lo repiten sin pensar, pero nunca leen la explicación, pero les encanta usarla como una excusa para no pensar , y como una manera barata y fácil de sentirse superior a los demás.
  4. eventualmente, ninguna cantidad de desacreditación razonable puede detener la marea del "meme", y el paradigma degenera en religión y dogma.

El meme, originalmente destinado a llevar a los n00bs a la iluminación, ahora se usa como un club para golpearlos hasta dejarlos inconscientes.

La composición y la herencia son cosas muy diferentes, y no deben confundirse entre sí. Si bien es cierto que la composición se puede utilizar para simular la herencia con mucho trabajo adicional , esto no convierte a la herencia en un ciudadano de segunda clase, ni hace de la composición el hijo favorito. El hecho de que muchos n00bs intenten usar la herencia como un acceso directo no invalida el mecanismo, y casi todos los n00bs aprenden del error y, por lo tanto, mejoran.

Por favor PIENSE acerca de sus diseños, y dejar de chorros consignas.

</rant>

Steven A. Lowe
fuente
16
Booch argumenta que la herencia de implementación introduce un alto acoplamiento entre clases. Por otro lado, considera que la herencia es el concepto clave que distingue a la POO de la programación procesal.
Nemanja Trifunovic
13
Debería haber una palabra para este tipo de cosas. Regurgitación prematura , tal vez?
Jörg W Mittag
8
@ Jörg: ¿Sabes qué pasará con un término como Regurgitación prematura? Exactamente lo que se explica arriba. :) (Por cierto, ¿cuándo la regurgitación nunca es prematura?)
Bjarke Freund-Hansen
66
@Nemanja: Correcto. La pregunta es si este acoplamiento es realmente malo. Si las clases están conceptualmente fuertemente acopladas y conceptualmente forman una relación supertipo-subtipo, y nunca podrían ser desacopladas de manera conceptual, incluso si están formalmente desacopladas a nivel del lenguaje, entonces el acoplamiento fuerte está bien.
dsimcha
55
El mantra es simple, "solo porque puedes darle forma a play-doh para que se vea como algo no significa que debas hacer todo con play-doh". ;)
Evan Plaice
73

Experiencia.

Como usted dice, son herramientas para diferentes trabajos, pero la frase surgió porque las personas no la usaban de esa manera.

La herencia es principalmente una herramienta polimórfica, pero algunas personas, para su mayor peligro, intentan usarla como una forma de reutilizar / compartir código. El razonamiento es "bueno si heredo, entonces obtengo todos los métodos de forma gratuita", pero ignorando el hecho de que estas dos clases potencialmente no tienen una relación polimórfica.

Entonces, ¿por qué favorecer la composición sobre la herencia? Bueno, simplemente porque la mayoría de las veces la relación entre clases no es polimórfica. Existe simplemente para ayudar a recordar a las personas que no respondan de manera instintiva heredando.

Stephen Bailey
fuente
77
Básicamente, no puedes decir "no deberías usar OOP en primer lugar si no entiendes la sustitución de Liskov", entonces dices "favorecer la composición sobre la herencia" en lugar de intentar limitar el daño causado por incompetentes codificadores?
Mason Wheeler
13
@Mason: Como cualquier mantra, "favorecer la composición sobre la herencia" está dirigido a programadores principiantes. Si ya sabe cuándo usar la herencia y cuándo usar la composición, entonces no tiene sentido repetir un mantra como ese.
Dean Harding
77
@Dean: no estoy seguro de que principiante sea ​​igual a uno que no entiende las mejores prácticas de herencia. Creo que hay más que eso. La mala herencia es la causa de muchos, muchos dolores de cabeza en mi trabajo y no es un código escrito por programadores que se considerarían "principiantes".
Nicole
66
@Renesis: Lo primero en lo que pienso cuando escucho eso es que "algunas personas tienen diez años de experiencia y otras tienen un año de experiencia repetido diez veces".
Mason Wheeler
8
Estaba diseñando un proyecto bastante grande justo después de la primera "Obtención" de OO y confié mucho en él. Aunque funcionó bien, constantemente encontré el diseño quebradizo, causando irritaciones menores aquí y allá y, a veces, convirtiendo a ciertos refactores en una perra completa. Es un poco difícil explicar toda la experiencia, pero la frase "Favorecer la compilación sobre la herencia" lo describe EXACTAMENTE. Tenga en cuenta que no dice ni siquiera implica "Evitar la herencia", simplemente le da un pequeño empujón cuando la elección no es obvia.
Bill K
43

No es una idea nueva, creo que en realidad se introdujo en el libro de patrones de diseño GoF, que se publicó en 1994.

El principal problema con la herencia es que es una caja blanca. Por definición, necesita conocer los detalles de implementación de la clase de la que está heredando. Con la composición, por otro lado, solo te importa la interfaz pública de la clase que estás componiendo.

Del libro de GoF:

La herencia expone una subclase a los detalles de la implementación de su padre, a menudo se dice que 'la herencia rompe la encapsulación'

El artículo de Wikipedia en el libro GoF tiene una introducción decente.

Dean Harding
fuente
14
No estoy de acuerdo con eso No necesita conocer los detalles de implementación de la clase de la que está heredando; solo el público y los miembros protegidos expuestos por la clase. Si tiene que conocer los detalles de implementación, entonces usted o la persona que escribió la clase base está haciendo algo mal, y si la falla está en la clase base, la composición no lo ayudará a solucionarla.
Mason Wheeler
18
¿Cómo puedes estar en desacuerdo con algo que no has leído? Hay una página sólida y media de discusión de GoF de la que obtienes solo una pequeña vista.
philosodad
77
@pholosodad: No estoy en desacuerdo con algo que no he leído. Estoy en desacuerdo con lo que escribió Dean: "Por definición, debes conocer los detalles de implementación de la clase de la que estás heredando", que he leído.
Mason Wheeler
10
Lo que escribí es solo un resumen de lo que se describe en el libro GoF. Podría haberlo redactado un poco (no es necesario que conozca todos los detalles de implementación), pero esa es la razón general por la cual el GoF dice que favorece la composición sobre la herencia.
Dean Harding
2
Corríjame si me equivoco pero lo veo como "favorecer las relaciones de clase explícitas (es decir, las interfaces) sobre las implícitas (es decir, la herencia)". Los primeros le dicen lo que necesita sin decirle cómo hacerlo. Este último no solo te dice cómo hacerlo, sino que también hará que te arrepientas en el futuro.
Evan Plaice
26

Para responder una parte de su pregunta, creo que este concepto apareció por primera vez en el libro GOF Design Patterns: Elements of Reusable Object-Oriented Software , que se publicó por primera vez en 1994. La frase aparece en la parte superior de la página 20, en la introducción:

Favorecer la composición de objetos sobre la herencia

Prefacio esta declaración con una breve comparación de la herencia con la composición. No dicen "nunca use la herencia".

vjones
fuente
23

"Composición sobre herencia" es una forma corta (y aparentemente engañosa) de decir "Cuando sienta que los datos (o el comportamiento) de una clase deben incorporarse a otra clase, siempre considere usar la composición antes de aplicar la herencia a ciegas".

¿Por qué es esto cierto? Porque la herencia crea un estrecho acoplamiento en tiempo de compilación entre las 2 clases. La composición en contraste es un acoplamiento flexible, que entre otros permite una clara separación de preocupaciones, la posibilidad de cambiar dependencias en tiempo de ejecución y una comprobabilidad de dependencia más fácil y aislada.

Eso solo significa que la herencia debe manejarse con cuidado porque tiene un costo, no es que no sea útil. En realidad, "Composición sobre herencia" a menudo termina siendo "Composición + herencia sobre herencia" ya que a menudo desea que su dependencia compuesta sea una superclase abstracta en lugar de la subclase concreta. Le permite cambiar entre diferentes implementaciones concretas de su dependencia en tiempo de ejecución.

Por esa razón (entre otras), probablemente verá que la herencia se usa con más frecuencia en forma de implementación de interfaz o clases abstractas que la herencia de vainilla.

Un ejemplo (metafórico) podría ser:

"Tengo una clase Snake y quiero incluir como parte de esa clase lo que sucede cuando Snake muerde. Me sentiría tentado de que Snake herede una clase BiterAnimal que tenga el método Bite () y anule ese método para reflejar la mordida venenosa Pero Composition over Inheritance me advierte que debería intentar usar composición en su lugar ... En mi caso, esto podría traducirse en que Snake tenga un miembro Bite. La clase Bite podría ser abstracta (o una interfaz) con varias subclases. Esto permitiría cosas buenas como tener las subclases VenomousBite y DryBite y poder cambiar la mordida en la misma instancia de Snake a medida que la serpiente envejece. Además, manejar todos los efectos de una Mordida en su propia clase separada podría permitirme reutilizarla en esa clase Frost , porque la escarcha muerde pero no es un BiterAnimal, y así sucesivamente ... "

guillaume31
fuente
44
Muy buen ejemplo con la serpiente. Conocí un caso similar recientemente con mis propias clases
Maksee
22

Algunos posibles argumentos para la composición:

La composición es un poco más
herencia de lenguaje / marco agnóstico y lo que impone / requiere / habilita diferirá entre los idiomas en términos de a qué tiene acceso la subclase / superclase y qué implicaciones de rendimiento puede tener wrt métodos virtuales, etc. La composición es bastante básica y requiere muy poco soporte de lenguaje y, por lo tanto, las implementaciones en diferentes plataformas / marcos pueden compartir patrones de composición más fácilmente.

La composición es una forma muy simple y táctil de construir objetos.
La herencia es relativamente fácil de entender, pero aún así no se demuestra tan fácilmente en la vida real. Muchos objetos en la vida real se pueden descomponer en partes y componer. Digamos que una bicicleta se puede construir con dos ruedas, un cuadro, un asiento, una cadena, etc. Se explica fácilmente por la composición. Mientras que en una metáfora de la herencia se podría decir que una bicicleta extiende un monociclo, algo factible pero aún más lejos de la imagen real que la composición (obviamente, este no es un muy buen ejemplo de herencia, pero el punto sigue siendo el mismo). Incluso la palabra herencia (al menos para la mayoría de los hablantes de inglés de EE. UU., Esperaría) invoca automáticamente un significado en la línea "Algo transmitido por un pariente fallecido" que tiene cierta correlación con su significado en el software, pero todavía se ajusta de manera flexible.

La composición es casi siempre más flexible.
Con la composición siempre puede elegir definir su propio comportamiento o simplemente exponer esa parte de sus partes compuestas. De esta manera, no enfrenta ninguna de las restricciones que puede imponer una jerarquía de herencia (virtual frente a no virtual, etc.)

Entonces, podría deberse a que la Composición es naturalmente una metáfora más simple que tiene menos restricciones teóricas que la herencia. Además, estas razones particulares pueden ser más evidentes durante el tiempo de diseño, o posiblemente sobresalir cuando se trata con algunos de los puntos dolorosos de la herencia.

Descargo de responsabilidad:
Obviamente no es esta calle clara / unidireccional. Cada diseño merece la evaluación de varios patrones / herramientas. La herencia se usa ampliamente, tiene muchos beneficios y muchas veces es más elegante que la composición. Estas son solo algunas de las posibles razones que uno podría usar para favorecer la composición.

TJB
fuente
1
"Obviamente no es esta calle clara / unidireccional". ¿Cuándo la composición no es más (o igualmente) flexible que la herencia? Yo diría que es una calle de sentido único. La herencia es solo azúcar sintáctica para un caso especial de composición.
weberc2
La composición a menudo no es flexible cuando se usa un lenguaje que implementa la composición usando interfaces implementadas explícitamente , porque esas interfaces no pueden evolucionar de manera compatible con el tiempo. #jmtcw
MikeSchinkel
11

Quizás haya notado que la gente dice esto en los últimos meses, pero los buenos programadores lo saben desde hace mucho más tiempo. Ciertamente lo he estado diciendo cuando sea apropiado durante aproximadamente una década.

El punto del concepto es que hay una gran sobrecarga conceptual para la herencia. Cuando usa la herencia, cada llamada de método tiene un despacho implícito. Si tiene árboles de herencia profundos, o despacho múltiple, o (aún peor) ambos, entonces averiguar dónde se enviará el método en particular en cualquier llamada en particular puede convertirse en un PITA real. Hace que el razonamiento correcto sobre el código sea más complejo y dificulta la depuración.

Permítanme dar un ejemplo simple para ilustrar. Supongamos que en el fondo de un árbol de herencia, alguien nombra un método foo. Entonces alguien más aparece y agrega fooen la parte superior del árbol, pero haciendo algo diferente. (Este caso es más común con la herencia múltiple). Ahora esa persona que trabaja en la clase raíz ha roto la oscura clase secundaria y probablemente no se da cuenta. Podría tener una cobertura del 100% con las pruebas unitarias y no notar esta ruptura porque la persona en la parte superior no pensaría en probar la clase infantil, y las pruebas para la clase infantil no piensan en probar los nuevos métodos creados en la parte superior . (Es cierto que hay formas de escribir pruebas unitarias que captarán esto, pero también hay casos en los que no es fácil escribir pruebas de esa manera).

Por el contrario, cuando usa la composición, en cada llamada generalmente es más claro a qué está enviando la llamada. (De acuerdo, si está utilizando la inversión de control, por ejemplo, con la inyección de dependencia, averiguar dónde va la llamada también puede ser problemático. Pero, por lo general, es más sencillo de resolver). Esto facilita el razonamiento al respecto. Como beneficio adicional, la composición da como resultado que los métodos estén separados entre sí. El ejemplo anterior no debería suceder allí porque la clase secundaria pasaría a algún componente oscuro, y nunca hay una pregunta sobre si la llamada a fooestaba destinada al componente oscuro o al objeto principal.

Ahora tiene toda la razón en que la herencia y la composición son dos herramientas muy diferentes que sirven a dos tipos diferentes de cosas. La herencia segura conlleva una sobrecarga conceptual, pero cuando es la herramienta adecuada para el trabajo, conlleva menos sobrecarga conceptual que tratar de no usarla y hacer a mano lo que hace por usted. Nadie que sepa lo que está haciendo diría que nunca debes usar la herencia. Pero asegúrese de que sea lo correcto.

Desafortunadamente, muchos desarrolladores aprenden sobre software orientado a objetos, aprenden sobre herencia y luego salen a usar su nuevo hacha con la mayor frecuencia posible. Lo que significa que intentan usar la herencia donde la composición era la herramienta correcta. Esperemos que aprendan mejor a tiempo, pero con frecuencia esto no sucede hasta después de quitar algunas extremidades, etc. Decirles por adelantado que es una mala idea tiende a acelerar el proceso de aprendizaje y reducir las lesiones.

btilly
fuente
3
Muy bien, supongo que tiene sentido desde una perspectiva de C ++. Eso es algo en lo que nunca pensé, porque no es un problema en Delphi, que es lo que uso la mayor parte del tiempo. (No hay herencia múltiple, y si tiene un método en una clase base y otro método con el mismo nombre en una clase derivada que no anula el método base, el compilador emitirá una advertencia, por lo que no terminará accidentalmente con este tipo de problema.)
Mason Wheeler
1
@Mason: la versión de Ander de Object Pascal (también conocido como Delphi) es superior a C ++ y Java cuando se trata del frágil problema de la herencia de la clase base. A diferencia de C ++ y Java, la sobrecarga de un método virtual heredado no está implícita.
bit-twiddler
2
@ bit-twiddler, lo que dices sobre C ++ y Java se puede decir sobre Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C y cualquier otro lenguaje que haya aprendido que proporcione cualquier forma de soporte OO. Dicho esto, googlear sugiere que C # sigue el ejemplo de Object Pascal.
btilly
3
Eso es porque Anders Hejlsberg diseñó el sabor de Borland de Object Pascal (también conocido como Delphi) y C #.
bit-twiddler
8

Es una reacción a la observación de que los principiantes de OO tienden a usar la herencia cuando no la necesitan. La herencia ciertamente no es algo malo, pero se puede usar en exceso. Si una clase solo necesita funcionalidad de otra, entonces la composición probablemente funcionará. En otras circunstancias, la herencia funcionará y la composición no.

Heredar de una clase implica muchas cosas. Implica que un Derivado es un tipo de Base (vea el principio de Sustitución de Liskov para los detalles sangrientos), ya que cada vez que use una Base tendría sentido usar un Derivado. Otorga acceso derivado a los miembros protegidos y las funciones miembro de Base. Es una relación estrecha, lo que significa que tiene un alto acoplamiento, y es más probable que los cambios en uno requieran cambios en el otro.

El acoplamiento es algo malo. Hace que los programas sean más difíciles de entender y modificar. En igualdad de condiciones, siempre debe seleccionar la opción con menos acoplamiento.

Por lo tanto, si la composición o la herencia harán el trabajo de manera efectiva, elija la composición, porque es un acoplamiento más bajo. Si la composición no hará el trabajo de manera efectiva, y la herencia lo hará, elija la herencia, porque tiene que hacerlo.

David Thornley
fuente
"En otras circunstancias, la herencia funcionará y la composición no". ¿Cuando? La herencia se puede modelar usando la composición y el polimorfismo de la interfaz, por lo que me sorprendería si hay circunstancias en las que la composición no funcione.
weberc2
8

Aquí están mis dos centavos (más allá de todos los excelentes puntos ya planteados):

En mi humilde opinión, todo se reduce al hecho de que la mayoría de los programadores realmente no obtienen la herencia, y terminan exagerando, en parte como resultado de cómo se enseña este concepto. Este concepto existe como una forma de tratar de disuadirlos de exagerar, en lugar de enfocarse en enseñarles cómo hacerlo correctamente.

Cualquiera que haya pasado tiempo enseñando o asesorando sabe que esto es lo que sucede a menudo, especialmente con los nuevos desarrolladores que tienen experiencia en otros paradigmas:

Estos desarrolladores inicialmente sienten que la herencia es este concepto aterrador. Por lo tanto, terminan creando tipos con superposiciones de interfaz (por ejemplo, el mismo comportamiento especificado sin subtipo común) y con globales para implementar piezas comunes de funcionalidad.

Luego (a menudo como resultado de una enseñanza demasiado entusiasta), aprenden sobre los beneficios de la herencia, pero a menudo se les enseña como la solución general para la reutilización. Terminan con la percepción de que cualquier comportamiento compartido debe ser compartido a través de la herencia. Esto se debe a que a menudo se centra en la herencia de implementación en lugar de subtipar.

En el 80% de los casos eso es lo suficientemente bueno. Pero el otro 20% es donde comienza el problema. En aras de evitar la reescritura y para asegurarse de que han aprovechado la implementación compartida, comienzan a diseñar su jerarquía en torno a la implementación prevista en lugar de las abstracciones subyacentes. La "pila hereda de una lista doblemente vinculada" es un buen ejemplo de esto.

La mayoría de los maestros no insisten en introducir el concepto de interfaces en este punto, porque es otro concepto o porque (en C ++) hay que simularlos usando clases abstractas y herencia múltiple que no se enseña en esta etapa. En Java, muchos maestros no distinguen la "herencia múltiple no" o "la herencia múltiple es mala" de la importancia de las interfaces.

Todo esto se ve agravado por el hecho de que hemos aprendido tanto de la belleza de no tener que escribir código superfluo con la herencia de implementación, que toneladas de código de delegación simple no parece natural. Por lo tanto, la composición parece desordenada, por lo que necesitamos estas reglas generales para presionar a los nuevos programadores a usarlas de todos modos (lo cual también exageran).

Uri
fuente
Eso es una parte verdadera de la educación. Tienes que hacer los cursos superiores para obtener, es decir, el resto del 20%, pero tú, como estudiante, acabas de recibir los cursos básicos y quizás intermedios. Al final, te sientes bien enseñado porque hiciste bien tus cursos (y simplemente no lo ves solo como un alfiler en la escalera). Es solo una parte de la realidad y nuestra mejor apuesta es comprender sus efectos secundarios, no atacar a los programadores.
Independiente
8

En uno de los comentarios, Mason mencionó que un día estaríamos hablando de herencia considerada dañina .

Yo espero que sí.

El problema con la herencia es a la vez simple y mortal, no respeta la idea de que un concepto debe tener una funcionalidad.

En la mayoría de los idiomas OO, cuando hereda de una clase base usted:

  • heredar de su interfaz
  • heredar de su implementación (tanto datos como métodos)

Y aquí se convierten en el problema.

Este no es el único enfoque, aunque los 00 idiomas están atascados en su mayoría. Afortunadamente, existen interfaces / clases abstractas en ellas.

Además, la falta de facilidad para hacer lo contrario contribuye a hacer que se use en gran medida: francamente, incluso sabiendo esto, ¿heredaría de una interfaz e integraría la clase base por composición, delegando la mayoría de las llamadas a métodos? Sin embargo, sería considerablemente mejor, incluso se le advertiría si de repente aparece un nuevo método en la interfaz y tendría que elegir, conscientemente, cómo implementarlo.

Como contrapunto, Haskellsolo permita usar el Principio de Liskov cuando "derive" de interfaces puras (llamadas conceptos) (1). No puede derivar de una clase existente, solo la composición le permite incrustar sus datos.

(1) los conceptos pueden proporcionar un valor predeterminado razonable para una implementación, pero como no tienen datos, este valor predeterminado solo puede definirse en términos de los otros métodos propuestos por el concepto o en términos de constantes.

Matthieu M.
fuente
7

La respuesta simple es: la herencia tiene un mayor acoplamiento que la composición. Dadas dos opciones, de cualidades equivalentes, elija la que esté menos acoplada.

Jeffrey Faust
fuente
2
Pero ese es todo el punto. Son no "por lo demás equivalentes." La herencia permite la sustitución de Liskov y el polimorfismo, que son el objetivo de usar OOP. La composición no.
Mason Wheeler
44
El polimorfismo / LSP no son "el punto completo" de la POO, son una característica. También hay encapsulación, abstracciones, etc. La herencia implica una relación "es una", los modelos de agregación una relación "tiene una".
Steve
@Steve: puede hacer abstracciones y encapsular en cualquier paradigma que admita la creación de estructuras de datos y procedimientos / funciones. Pero el polimorfismo (que se refiere específicamente al envío de métodos virtuales en este contexto) y la sustitución de Liskov son exclusivos de la programación orientada a objetos. Eso es lo que quise decir con eso.
Mason Wheeler
3
@Mason: La directriz es "favorecer la composición sobre la herencia". De ninguna manera esto significa no usar o incluso evitar la herencia. Todo dice que cuando todas las demás cosas son iguales, elige la composición. Pero si necesitas herencia, ¡úsala!
Jeffrey Faust el
7

Creo que este tipo de consejo es como decir "prefiera conducir a volar". Es decir, los aviones tienen todo tipo de ventajas sobre los automóviles, pero eso conlleva una cierta complejidad. Entonces, si muchas personas intentan volar desde el centro de la ciudad hasta los suburbios, el consejo que realmente necesitan escuchar es que no necesitan volar, y de hecho, volar lo hará más complicado a largo plazo, incluso si parece genial / eficiente / fácil a corto plazo. Mientras que cuando usted no necesita volar, se supone generalmente que es obvio.

Del mismo modo, la herencia puede hacer cosas que la composición no puede, pero debe usarla cuando la necesite y no cuando no. Entonces, si nunca está tentado a asumir que necesita herencia cuando no la necesita, entonces no necesita el consejo de "preferir composición". Sin embargo, muchas personas lo hacen , y lo hacen necesario ese consejo.

Se supone que está implícito que cuando realmente necesitas herencia, es obvio, y debes usarla entonces.

Además, la respuesta de Steven Lowe. Realmente, realmente eso.

Jack V.
fuente
1
Me gusta tu analogía
barrylloyd 05 de
2

La herencia no es inherentemente mala, y la composición no es inherentemente buena. Son simplemente herramientas que un programador de OO puede usar para diseñar software.

Cuando miras una clase, ¿está haciendo más de lo que debería ( SRP )? ¿Está duplicando la funcionalidad innecesariamente ( DRY ), o está demasiado interesado en las propiedades o métodos de otras clases ( Feature Envy )? Si la clase está violando todos estos conceptos (y posiblemente más), ¿está tratando de ser una Clase de Dios ? Estos son una serie de problemas que pueden ocurrir al diseñar software, ninguno de los cuales es necesariamente un problema de herencia, pero que puede crear rápidamente grandes dolores de cabeza y dependencias frágiles donde también se ha aplicado el polimorfismo.

Es probable que la raíz del problema sea menos la falta de comprensión sobre la herencia, y más bien una mala elección en términos de diseño, o quizás no reconocer "olores de código" relacionados con clases que no se adhieren al Principio de Responsabilidad Única . El polimorfismo y la sustitución de Liskov no necesitan descartarse a favor de la composición. El polimorfismo en sí mismo puede aplicarse sin depender de la herencia, todos estos son conceptos bastante complementarios. Si se aplica cuidadosamente. El truco es tratar de mantener su código simple y limpio, y no sucumbir a estar demasiado preocupado por la cantidad de clases que necesita crear para crear un diseño de sistema robusto.

En cuanto a la cuestión de favorecer la composición sobre la herencia, este es realmente otro caso de la aplicación reflexiva de los elementos de diseño que tienen más sentido en términos del problema que se está resolviendo. Si no necesita heredar el comportamiento, entonces probablemente no debería, ya que la composición ayudará a evitar incompatibilidades y esfuerzos de refactorización más adelante. Si, por otro lado, descubre que está repitiendo una gran cantidad de código de modo que toda la duplicación se centre en un grupo de clases similares, entonces puede ser que crear un ancestro común ayude a reducir la cantidad de llamadas y clases idénticas Es posible que deba repetir entre cada clase. Por lo tanto, estás favoreciendo la composición, pero no estás asumiendo que la herencia nunca sea aplicable.

S.Robins
fuente
-1

La cita real proviene, creo, del "Java efectivo" de Joshua Bloch, donde es el título de uno de los capítulos. Afirma que la herencia es segura dentro de un paquete y donde sea que una clase haya sido específicamente diseñada y documentada para su extensión. Afirma, como otros han señalado, que la herencia rompe la encapsulación porque una subclase depende de los detalles de implementación de su superclase. Esto lleva, dice, a la fragilidad.

Aunque no lo menciona, me parece que la herencia única en Java significa que la composición le da mucha más flexibilidad que la herencia.

Vanessa Williams
fuente