Pregunta ¿Prefiere la composición sobre la herencia?


¿Por qué preferir la composición a la herencia? ¿Qué compensaciones hay para cada enfoque? ¿Cuándo debe elegir la herencia sobre la composición?


1348
2017-09-08 01:58


origen


Respuestas:


Prefiere la composición en lugar de la herencia, ya que es más maleable / fácil de modificar más tarde, pero no utilices un enfoque de redactar siempre. Con la composición, es fácil cambiar el comportamiento sobre la marcha con Dependency Injection / Setters. La herencia es más rígida ya que la mayoría de los idiomas no le permiten derivar de más de un tipo. Entonces el ganso está más o menos cocinado una vez que deriva de TypeA.

Mi prueba de ácido para lo anterior es:

  • ¿El Tipo B quiere exponer la interfaz completa (todos los métodos públicos, no menos) de la Tipo A de modo que el Tipo B se pueda usar donde se espera Tipo A? Indica Herencia.

p.ej. Un biplano Cessna expondrá la interfaz completa de un avión, si no más. Eso lo hace apropiado para derivar de Avión.

  • ¿El Tipo B solo quiere algo / parte del comportamiento expuesto por TypeA? Indica la necesidad de Composición. 

p.ej. Un pájaro puede necesitar solo el comportamiento de vuelo de un avión. En este caso, tiene sentido extraerlo como una interfaz / clase / y ambos y convertirlo en miembro de ambas clases.

Actualizar: Acabo de volver a mi respuesta y parece que ahora está incompleta sin una mención específica de Barbara Liskov Principio de sustitución de Liskov como una prueba para '¿Debería heredar de este tipo?'


1010
2017-09-10 03:04



Piensa en la contención como un tiene un relación. Un automóvil "tiene un" motor, una persona "tiene un" nombre, etc.

Piensa en la herencia como una es un relación. Un automóvil "es un" vehículo, una persona "es un" mamífero ", etc.

No me atribuyo ningún crédito por este enfoque. Lo tomé directamente de la Segunda edición de Code Complete por Steve McConnell, Sección 6.3.


347
2017-09-08 02:09



Si entiendes la diferencia, es más fácil de explicar.

Código de procedimiento

Un ejemplo de esto es PHP sin el uso de clases (particularmente antes de PHP5). Toda la lógica está codificada en un conjunto de funciones. Puede incluir otros archivos que contengan funciones auxiliares, y así sucesivamente, y llevar a cabo su lógica de negocios al pasar datos en funciones. Esto puede ser muy difícil de administrar a medida que la aplicación crece. PHP5 intenta remediar esto ofreciendo un diseño más orientado a objetos.

Herencia

Esto fomenta el uso de clases. La herencia es uno de los tres principios del diseño OO (herencia, polimorfismo, encapsulación).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

Esto es herencia en el trabajo. El empleado "es una" persona o hereda de una persona. Todas las relaciones de herencia son relaciones "es-a". El empleado también sombrea la propiedad del Título de Persona, lo que significa Employee.Title devolverá el Título para el Empleado, no la Persona.

Composición

La composición se ve favorecida por la herencia. Para decirlo de manera simple, tendrías:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

La composición es típicamente una relación "tiene una" o "usa una". Aquí la clase Empleado tiene una Persona. No hereda de Persona, sino que obtiene el objeto Persona que se le pasa, por lo que "tiene una" Persona.

Composición sobre herencia

Ahora di que quieres crear un tipo de administrador para que termines con:

class Manager : Person, Employee {
   ...
}

Este ejemplo funcionará bien, sin embargo, ¿qué pasa si la persona y el empleado declararon ambos? Title? ¿Debería Manager.Title devolver "Manager of Operations" o "Mr."? En composición, esta ambigüedad se maneja mejor:

Class Manager {
   public Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

El objeto Manager está compuesto como un Empleado y una Persona. El comportamiento del título se toma del empleado. Esta composición explícita elimina la ambigüedad entre otras cosas y encontrará menos errores.


175
2018-05-21 07:54



Con todos los beneficios innegables proporcionados por la herencia, estas son algunas de sus desventajas.

Desventajas de la herencia:

  1. No puede cambiar la implementación heredada de las súper clases en el tiempo de ejecución (obviamente porque la herencia se define en tiempo de compilación).
  2. La herencia expone una subclase a detalles de la implementación de clase de su padre, por eso a menudo se dice que la herencia rompe la encapsulación (en un sentido que realmente necesita enfocarse en las interfaces, no en la implementación, por lo que no siempre es preferible reutilizarlas por subclasificación).
  3. El estrecho acoplamiento proporcionado por la herencia hace que la implementación de una subclase esté muy ligada a la implementación de una superclase que cualquier cambio en la implementación principal obligará a la subclase a cambiar.
  4. La reutilización excesiva por subclasificación puede hacer que la pila de herencia sea muy profunda y muy confusa también.

Por otra parte Composición de objetos se define en tiempo de ejecución a través de objetos que adquieren referencias a otros objetos. En tal caso, estos objetos nunca podrán alcanzar los datos protegidos de los demás (sin interrupción de encapsulado) y se verán obligados a respetar la interfaz de cada uno. Y en este caso también, las dependencias de implementación serán mucho menos que en el caso de la herencia.


91
2018-05-21 08:34



Otra razón, muy pragmática, para preferir la composición sobre la herencia tiene que ver con su modelo de dominio y asignarlo a una base de datos relacional. Es realmente difícil mapear la herencia al modelo SQL (terminas con todo tipo de soluciones hacky, como crear columnas que no siempre se usan, usar vistas, etc.). Algunos ORML intentan lidiar con esto, pero siempre se complica rápidamente. La composición se puede modelar fácilmente a través de una relación de clave externa entre dos tablas, pero la herencia es mucho más difícil.


77
2017-09-08 02:48



Mientras que en palabras cortas estoy de acuerdo con "Prefiero la composición sobre la herencia", muy a menudo para mí suena como "preferir las patatas a la coca-cola". Hay lugares para la herencia y lugares para la composición. Necesitas entender la diferencia, entonces esta pregunta desaparecerá. Lo que realmente significa para mí es "si vas a usar la herencia, piensa de nuevo, es probable que necesites la composición".

Deberías preferir las papas a la coca cola cuando quieres comer, y la coca cola a las papas cuando quieres beber.

Crear una subclase debería significar algo más que una forma conveniente de llamar a los métodos de superclase. Debería usar la herencia cuando la subclase "es-una" superclase sea estructural y funcionalmente, cuando se puede usar como superclase y va a usar eso. Si no es el caso, no es herencia, sino algo más. Composición es cuando sus objetos consisten en otro, o tienen alguna relación con ellos.

Entonces, para mí, parece que si alguien no sabe si necesita herencia o composición, el verdadero problema es que no sabe si quiere beber o comer. Piense más sobre su dominio problemático, entiéndalo mejor.


64
2018-05-08 22:29



La herencia es bastante tentadora, especialmente viniendo de terrenos de procedimiento y a menudo se ve engañosamente elegante. Quiero decir que todo lo que necesito hacer es agregar esta funcionalidad a alguna otra clase, ¿verdad? Bueno, uno de los problemas es que

herencia es probablemente la peor forma de acoplamiento que puede tener

Su clase base rompe la encapsulación al exponer los detalles de implementación a las subclases en forma de miembros protegidos. Esto hace que tu sistema sea rígido y frágil. Sin embargo, la falla más trágica es que la nueva subclase trae consigo todo el bagaje y la opinión de la cadena de herencia.

El artículo, La herencia es el mal: el fracaso épico del DataAnnotationsModelBinder, repasa un ejemplo de esto en C #. Muestra el uso de la herencia cuando se debería haber usado la composición y cómo se puede refactorizar.


54
2018-01-23 19:55



En Java o C #, un objeto no puede cambiar su tipo una vez que se ha instanciado.

Por lo tanto, si su objeto necesita aparecer como un objeto diferente o comportarse de manera diferente según el estado o las condiciones del objeto, utilice Composición: Referirse a Estado y Estrategia Patrones de diseño.

Si el objeto debe ser del mismo tipo, utilice Herencia o implementar interfaces.


37
2017-09-08 02:25



Personalmente aprendí a preferir siempre la composición sobre la herencia. No hay problema programático que pueda resolver con herencia que no pueda resolver con la composición; aunque puede que tenga que usar Interfaces (Java) o Protocolos (Obj-C) en algunos casos. Como C ++ no sabe nada de eso, tendrá que usar clases base abstractas, lo que significa que no puede deshacerse por completo de la herencia en C ++.

La composición suele ser más lógica, proporciona una mejor abstracción, una mejor encapsulación, una mejor reutilización del código (especialmente en proyectos muy grandes) y es menos probable que rompa algo a distancia simplemente porque ha realizado un cambio aislado en cualquier parte de su código. También hace que sea más fácil mantener el "Principio de Responsabilidad Individual", que a menudo se resume como"Nunca debe haber más de una razón para que una clase cambie.", y significa que cada clase existe para un propósito específico y solo debe tener métodos que están directamente relacionados con su propósito. Además, tener un árbol de herencia muy poco profundo hace que sea mucho más fácil mantener la visión general incluso cuando su proyecto comienza a obtener realmente grande. Mucha gente piensa que la herencia representa nuestra mundo real bastante bien, pero esa no es la verdad. El mundo real usa mucha más composición que herencia. Casi todos los objetos del mundo real que puedes sostener en tu mano han sido compuestos de otros objetos más pequeños del mundo real.

Sin embargo, hay aspectos negativos de la composición. Si omite la herencia por completo y solo se enfoca en la composición, notará que a menudo tiene que escribir un par de líneas de código adicionales que no eran necesarias si había usado la herencia. También a veces te obligan a repetirte y esto viola el DRY Principio (SECO = No repetir) Además, la composición a menudo requiere delegación, y un método es simplemente llamar a otro método de otro objeto sin otro código que rodee esta llamada. Tales "llamadas al método doble" (que pueden extenderse fácilmente a llamadas a métodos triples o cuádruples e incluso más allá de eso) tienen un rendimiento mucho peor que la herencia, donde simplemente heredas un método de tu padre. Llamar a un método heredado puede ser tan rápido como llamar a uno no heredado, o puede ser un poco más lento, pero generalmente es más rápido que dos llamadas a métodos consecutivos.

Puede haber notado que la mayoría de los lenguajes OO no permiten herencia múltiple. Si bien hay un par de casos en que la herencia múltiple realmente puede comprar algo, pero esas son más bien excepciones que la regla. Cada vez que se encuentre con una situación en la que piense que "la herencia múltiple sería una característica realmente genial para resolver este problema", generalmente se encuentra en un punto en el que debe volver a pensar en la herencia, ya que incluso puede requerir un par de líneas de código adicionales. , una solución basada en la composición generalmente resultará ser mucho más elegante, flexible y a prueba de futuro.

La herencia es realmente una característica interesante, pero me temo que se ha usado en exceso en los últimos años. Las personas trataban la herencia como el único martillo que puede clavarlo todo, independientemente de si era en realidad un clavo, un tornillo o tal vez algo completamente diferente.


27
2018-01-31 19:34