Estoy tratando de enseñarme ingeniería de software y encontrar información contradictoria que me confunde.
He estado aprendiendo OOP y qué son las clases / interfaces abstractas y cómo usarlas, pero luego estoy leyendo que uno debería 'favorecer la composición sobre la herencia'. Entiendo que la composición es cuando una clase compone / crea un objeto de otra clase para utilizar / interactuar con la funcionalidad de ese nuevo objeto.
Entonces mi pregunta es ... ¿Se supone que no debo usar clases e interfaces abstractas? ¿Para no crear una clase abstracta y extender / heredar la funcionalidad de esa clase abstracta en las clases concretas, y en su lugar, simplemente componer nuevos objetos para usar la funcionalidad de la otra clase?
O, ¿debo usar composición Y heredar de clases abstractas? usándolos a ambos juntos? Si es así, ¿podría proporcionar algunos ejemplos de cómo funcionaría eso y sus beneficios?
Como me siento más cómodo con PHP, lo estoy usando para mejorar mis habilidades de OOP / SE antes de pasar a otros idiomas y transferir mis habilidades de SE recién adquiridas, por lo que los ejemplos que usan PHP serían muy apreciados.
fuente
Respuestas:
Composición sobre herencia significa que cuando desea reutilizar o ampliar la funcionalidad de una clase existente, a menudo es más apropiado crear otra clase que 'envolverá' la clase existente y usará su implementación internamente. El patrón decorador es un ejemplo de esto.
La herencia no es una buena forma predeterminada de lidiar con escenarios de reutilización porque a menudo desea utilizar solo una parte de una funcionalidad de clase base y la subclase no puede soportar todo el contrato de la clase base de una manera que satisfaga la sustitución de Liskov principio .
El método de plantilla es un buen ejemplo de un caso en el que la herencia es apropiada.
Consulte esta respuesta para obtener pautas sobre cómo elegir entre composición y herencia.
fuente
No estoy lo suficientemente familiarizado con PHP para darle ejemplos concretos en ese lenguaje, pero aquí hay algunas pautas que he encontrado útiles.
Las interfaces / rasgos son útiles para definir el comportamiento en muchas clases que pueden tener muy poco más en común.
Por ejemplo, si está trabajando en una API JSON, puede definir una interfaz que especifique dos métodos: "toJson" y "toStatusCode". Esto le permitiría escribir un ayudante que pueda tomar cualquier objeto que implemente esta interfaz y convertirlo en una respuesta Http.
La mayoría de las veces, esto es preferible a la herencia directa, porque es menos probable que sufra el frágil problema de la clase base, ya que tiende a jerarquías de clase poco profundas.
La herencia directa se considera mejor como algo que haces cuando hay razones de peso para hacerlo, en lugar de algo que haces a menos que haya razones de peso para no hacerlo.
En los idiomas que no admiten implementaciones predeterminadas en las interfaces, puede ser una forma razonable de compartir un conjunto de funciones básicas, pero generalmente restringe en gran medida lo que puede hacer con las subclases (la herencia múltiple, cuando está disponible, rara vez vale la pena) . A menudo, creo que vale la pena escribir los métodos de redireccionamiento repetitivo para usar la composición.
La composición debería ser tu estrategia predeterminada. En la medida de lo posible, componga con interfaces y páselas como argumentos de constructor. Esto hará que tu vida sea mucho más fácil al escribir exámenes.
Evite trabajar con singletons globales tanto como sea posible, ya que comparar la salida esperada y la real es un problema si parte de la salida depende del estado del reloj del sistema.
Esto, por supuesto, no siempre es posible. Por ejemplo, el marco web de Play proporciona una biblioteca JSON que es básicamente un lenguaje específico de dominio. Cambiar esto sería una tarea importante, de la cual, reemplazar las llamadas a 'Json.prettyPrint' sería solo una parte muy menor.
fuente
La composición es cuando una clase ofrece alguna funcionalidad al crear una instancia de una clase (posiblemente interna) que ya implementa esta funcionalidad, en lugar de heredar de esa clase.
Entonces, por ejemplo, si tiene una clase que modela un barco, y ahora le dicen que su barco debe ofrecer un helipuerto, no es natural derivar su barco de un helipuerto, ¡en cambio, debería tener su nave contiene una clase de helipuerto y la expone a través de algún
Ship.getHelipad()
método.En años antiguos (hace una década más o menos), la gente solía ver la herencia como una forma rápida y fácil de agregar funcionalidad, por lo que había muchos ejemplos del tipo de "barco heredado del helipuerto", que, por supuesto, eran muy lamentables.
Pero la frase "Favorecer la composición sobre la herencia" se ha redactado cuidadosamente para dejar en claro que esto es solo una sugerencia, no una regla. El autor del dicho fue lo suficientemente cuidadoso como para abstenerse de decir algo como " nunca usarás la herencia, solo la composición". Básicamente, esto llama la atención de la comunidad de ingenieros de software sobre el hecho de que la herencia se ha utilizado en exceso, mientras que en muchos casos, la composición produce diseños más claros, elegantes y sostenibles que la herencia.
Entonces, esencialmente, el dictamen "Favorecer la composición sobre la herencia" sugiere que cada vez que se enfrente con "¿heredar o componer?" pregunta, debería pensar detenidamente cuál es la estrategia más adecuada, y que lo más probable es que la estrategia más adecuada resulte ser composición, no herencia.
Pero como esto no es una regla, también debe tener en cuenta que hay muchos casos en los que la herencia es más natural. Si usa la composición allí donde debería haber usado la herencia, muchos males caerán sobre su código.
Para volver al ejemplo del barco, si su barco necesita ofrecer una interfaz para interactuar con un
FloatingMachine
, entonces es más natural derivarlo de unaFloatingMachine
clase abstracta , que muy posiblemente podría derivarse de otraMachine
clase abstracta .Aquí está la regla general para la respuesta a la pregunta de composición vs. herencia:
Un barco "es una" máquina flotante, y una máquina flotante "es una" máquina. Entonces, la herencia está perfectamente bien para aquellos. Pero un barco no es, por supuesto, un helipuerto. Por lo tanto, es mejor componer la funcionalidad del helipuerto.
fuente