oop - que - Un uso para la herencia mĂșltiple?
herencia multiple java (12)
¿Alguien puede pensar en alguna situación para usar herencia múltiple? Cada caso que puedo pensar puede ser resuelto por el operador del método
AnotherClass() { return this->something.anotherClass; }
Creo que el ejemplo de FMSF es una mala idea. Un auto no es un neumático o un motor. Deberías usar composición para eso.
MI (de implementación o interfaz) se puede usar para agregar funcionalidad. A menudo se llaman clases mixin ... Imagine que tiene una GUI. Hay una clase de vista que maneja el dibujo y una clase de arrastrar y soltar que maneja el arrastre. Si tiene un objeto que hace ambas cosas, tendría una clase como
class DropTarget{
public void Drop(DropItem & itemBeingDropped);
...
}
class View{
public void Draw();
...
}
/* View you can drop items on */
class DropView:View,DropTarget{
}
Creo que sería más útil para el código repetitivo. Por ejemplo, el patrón IDisposable es exactamente el mismo para todas las clases en .NET. Entonces, ¿por qué volver a escribir ese código una y otra vez?
Otro ejemplo es ICollection. La gran mayoría de los métodos de interfaz se implementan exactamente de la misma manera. Solo hay un par de métodos que son únicos para su clase.
Lamentablemente, la herencia múltiple es muy fácil de abusar. La gente comenzará rápidamente a hacer cosas tontas como la clase LabelPrinter heredará de su clase TcpIpConnector en lugar de limitarse a contenerla.
El siguiente ejemplo es principalmente algo que veo a menudo en C ++: a veces puede ser necesario debido a las clases de utilidad que necesitas pero debido a su diseño no se puede usar a través de la composición (al menos no eficientemente o sin hacer que el código sea aún más complicado que volver a caer herencia mult.). Un buen ejemplo es que tiene una clase base abstracta A y una clase derivada B, y B también necesita ser una clase de clase serializable, por lo que debe derivar, digamos, de otra clase abstracta llamada Serializable. Es posible evitar MI, pero si Serializable solo contiene unos pocos métodos virtuales y necesita un acceso profundo a los miembros privados de B, entonces valdría la pena enturbiar el árbol de herencia solo para evitar hacer declaraciones de amigos y regalar acceso a las partes internas de B a algunos clase de composición auxiliar.
Encuentro que la herencia múltiple es particularmente útil cuando uso clases mixin .
Como se indica en la Wikipedia:
En los lenguajes de programación orientados a objetos, un mixin es una clase que proporciona una cierta funcionalidad para ser heredada por una subclase, pero no para ser independiente.
Un ejemplo de cómo nuestro producto usa clases de mixin es para propósitos de guardar y restaurar la configuración. Hay una clase mixin abstracta que define un conjunto de métodos virtuales puros. Cualquier clase que se pueda guardar hereda de la clase mixin save / restore que automáticamente les da la funcionalidad de guardar / restaurar apropiada.
Pero también pueden heredar de otras clases como parte de su estructura de clase normal, por lo que es bastante común que estas clases utilicen herencia múltiple a este respecto.
Un ejemplo de herencia múltiple:
class Animal
{
virtual void KeepCool() const = 0;
}
class Vertebrate
{
virtual void BendSpine() { };
}
class Dog : public Animal, public Vertebrate
{
void KeepCool() { Pant(); }
}
Lo más importante al hacer cualquier forma de herencia pública (individual o múltiple) es respetar la relación. Una clase solo debe heredar de una o más clases si "es" uno de esos objetos. Si simplemente "contiene" uno de esos objetos, se debe usar agregación o composición en su lugar.
El ejemplo anterior está bien estructurado porque un perro es un animal y también un vertebrado.
Java tiene interfaces. C ++ no tiene.
Por lo tanto, la herencia múltiple se puede usar para emular la función de interfaz. Si usted es un programador C # y Java, cada vez que usa una clase que amplía una clase base pero también implementa algunas interfaces, está admitiendo que la herencia múltiple puede ser útil en algunas situaciones.
Un caso en el que trabajé recientemente involucraba impresoras de etiquetas habilitadas para redes. Necesitamos imprimir etiquetas, entonces tenemos una clase LabelPrinter. Esta clase tiene llamadas virtuales para imprimir varias etiquetas diferentes. También tengo una clase genérica para cosas conectadas TCP / IP, que pueden conectarse, enviar y recibir. Entonces, cuando necesité implementar una impresora, heredó de la clase LabelPrinter y de la clase TcpIpConnector.
Es cierto que la composición de una interfaz (similar a Java o C #) más el reenvío a un helper puede emular muchos de los usos comunes de herencia múltiple (especialmente mixins). Sin embargo, esto se hace a costa de que se repita ese código de reenvío (y que viole DRY).
MI abre una serie de áreas difíciles, y más recientemente algunos diseñadores de idiomas han tomado decisiones que los peligros potenciales de MI superan los beneficios.
Del mismo modo, uno puede argumentar en contra de los genéricos (los contenedores heterogéneos sí funcionan, los bucles pueden ser reemplazados con recursión (cola)) y casi cualquier otra característica de los lenguajes de programación. El hecho de que sea posible trabajar sin una función no significa que esa característica no tenga ningún valor o no pueda ayudar a expresar soluciones de manera efectiva.
Una gran diversidad de idiomas y familias de idiomas nos facilita a los desarrolladores elegir buenas herramientas que resuelvan el problema comercial. Mi caja de herramientas contiene muchos elementos que rara vez uso, pero en esas ocasiones no quiero tratar todo como un clavo.
Sospecho que en C ++, MI es el mejor uso como parte de un marco (las clases mix-in previamente discutidas). Lo único que sé con certeza es que cada vez que he tratado de usarlo en mis aplicaciones, he terminado lamentando la elección y, a menudo, arrancándola y reemplazándola con código generado.
MI es uno más de esos que "lo usan si realmente lo necesitan, pero asegúrese de que realmente lo necesitan".
Un ejemplo de cómo nuestro producto usa clases de mixin es para propósitos de guardar y restaurar la configuración. Hay una clase mixin abstracta que define un conjunto de métodos virtuales puros. Cualquier clase que se pueda guardar hereda de la clase mixin save / restore que automáticamente les da la funcionalidad de guardar / restaurar apropiada.
Este ejemplo no ilustra realmente la utilidad de la herencia múltiple. Lo que se define aquí es una INTERFAZ. La herencia múltiple le permite heredar el comportamiento también. Cuál es el punto de mixins.
Un ejemplo; debido a la necesidad de preservar la compatibilidad con versiones anteriores, tengo que implementar mis propios métodos de serialización.
Entonces, cada objeto obtiene un método de lectura y almacenamiento como este.
Public Sub Store(ByVal File As IBinaryWriter)
Public Sub Read(ByVal File As IBinaryReader)
También quiero poder asignar y clonar objetos también. Entonces me gustaría esto en cada objeto.
Public Sub Assign(ByVal tObject As <Class_Name>)
Public Function Clone() As <Class_Name>
Ahora en VB6 tengo este código repetido una y otra vez.
Public Assign(ByVal tObject As ObjectClass)
Me.State = tObject.State
End Sub
Public Function Clone() As ObjectClass
Dim O As ObjectClass
Set O = New ObjectClass
O.State = Me.State
Set Clone = 0
End Function
Public Property Get State() As Variant
StateManager.Clear
Me.Store StateManager
State = StateManager.Data
End Property
Public Property Let State(ByVal RHS As Variant)
StateManager.Data = RHS
Me.Read StateManager
End Property
Tenga en cuenta que Statemanager es una secuencia que lee y almacena arrays de bytes.
Este código se repite docenas de veces.
Ahora en .NET puedo solucionar esto usando una combinación de genéricos y herencia. Mi objeto bajo la versión de .NET obtiene Assign, Clone, y State cuando heredan de MyAppBaseObject. Pero no me gusta el hecho de que cada objeto herede de MyAppBaseObject.
Prefiero mezclar en la interfaz Assign Clone AND BEHAVIOR. Mejor aún, mezcle por separado la interfaz Leer y Almacenar y luego mezcle en Asignar y Clonar. Sería un código más limpio en mi opinión.
Pero los tiempos en los que el comportamiento de reutilización está EN MARCHA para cuando uso Interface. Esto se debe a que el objetivo de la mayoría de las jerarquías de objetos NO es reutilizar el comportamiento sino definir con precisión la relación entre diferentes objetos. Para qué interfaces están diseñadas Entonces, si bien sería bueno que C # (o VB.NET) tuviera alguna capacidad para hacer esto, no es un show stopper en mi opinión.
La razón por la cual esto es incluso un problema es que C ++ perdió el balón al principio cuando se trataba de la interfaz frente a la cuestión de la herencia. Cuando OOP debutó, todos pensaron que la reutilización del comportamiento era la prioridad. Pero esto resultó ser una quimera y solo útil para circunstancias específicas, como crear un marco de interfaz de usuario.
Más tarde, se desarrolló la idea de mixins (y otros conceptos relacionados en la programación orientada a aspectos). Se encontró que la herencia múltiple era útil para crear mix-ins. Pero C # se desarrolló justo antes de que esto fuera ampliamente reconocido. Probablemente se desarrollará una sintaxis alternativa para hacer esto.
Tuve que usarlo hoy, en realidad ...
Aquí estaba mi situación: tenía un modelo de dominio representado en la memoria donde una A contenía cero o más B (representados en una matriz), cada B tenía cero o más C y Cs a Ds. No pude cambiar el hecho de que eran matrices (la fuente de estas matrices provenía del código generado automáticamente del proceso de compilación). Cada instancia necesitaba hacer un seguimiento del índice en la matriz principal a la que pertenecían. También necesitaban hacer un seguimiento de la instancia de su padre (demasiados detalles sobre por qué). Escribí algo así (había más, y esto no es sintácticamente correcto, es solo un ejemplo):
class Parent
{
add(Child c)
{
children.add(c);
c.index = children.Count-1;
c.parent = this;
}
Collection<Child> children
}
class Child
{
Parent p;
int index;
}
Luego, para los tipos de dominio, hice esto:
class A : Parent
class B : Parent, Child
class C : Parent, Child
class D : Child
La implementación real fue en C # con interfaces y genéricos, y no pude hacer la herencia múltiple como lo haría si el lenguaje lo soportara (algunos copiaron y pegaron). Entonces, pensé que buscaría SO para ver lo que las personas piensan sobre la herencia múltiple, y recibí su pregunta;)
No pude usar su solución de .anotherClass, debido a la implementación de add para Parent (hace referencia a esto, y yo quería que esta no fuera otra clase).
Empeoró porque el código generado tenía una subclase algo diferente que no era ni un padre ni un hijo ... más copiar pegar.
La mayoría de los usos de la herencia múltiple a gran escala son para mixins. Como ejemplo:
class DraggableWindow : Window, Draggable { }
class SkinnableWindow : Window, Skinnable { }
class DraggableSkinnableWindow : Window, Draggable, Skinnable { }
etc ...
En la mayoría de los casos, es mejor usar herencia múltiple para hacer estrictamente herencia de interfaz.
class DraggableWindow : Window, IDraggable { }
Luego implementa la interfaz IDraggable en su clase DraggableWindow. Es muy difícil escribir buenas clases de mixin.
El beneficio del enfoque MI (incluso si solo está utilizando Interface MI) es que puede tratar todo tipo de Windows como objetos Window, pero tiene la flexibilidad de crear cosas que no serían posibles (o más difíciles) con single herencia.
Por ejemplo, en muchos marcos de clases, usted ve algo como esto:
class Control { }
class Window : Control { }
class Textbox : Control { }
Ahora, supongamos que quiere un cuadro de texto con características de ventana? Como ser dragable, tener una barra de título, etc. Podrías hacer algo como esto:
class WindowedTextbox : Control, IWindow, ITexbox { }
En el modelo de herencia única, no puede heredar fácilmente desde la ventana y el cuadro de texto sin tener algunos problemas con objetos de control duplicados y otros tipos de problemas. También puede tratar un WindowedTextbox como una ventana, un cuadro de texto o un control.
Además, para abordar su idioma de .anotherClass (), .anotherClass () devuelve un objeto diferente, mientras que la herencia múltiple permite que el mismo objeto se utilice para diferentes propósitos.
La mayoría de las personas usa la herencia múltiple en el contexto de la aplicación de múltiples interfaces a una clase. Este es el enfoque que aplican Java y C #, entre otros.
C ++ le permite aplicar múltiples clases básicas bastante libremente, en una relación is-a entre tipos. Por lo tanto, puede tratar un objeto derivado como cualquiera de sus clases base.
Otro uso, como señala LeopardSkinPillBoxHat , es en mezclas. Un excelente ejemplo de esto es la biblioteca Loki , del libro de Andrei Alexandrescu Modern C ++ Design. Utiliza lo que él denomina clases de política que especifican el comportamiento o los requisitos de una clase dada a través de la herencia.
Sin embargo, otro uso es uno que simplifica un enfoque modular que permite la independencia de API a través del uso de delegación de clase hermana en la temida jerarquía de diamante.
Los usos para MI son muchos. El potencial de abuso es aún mayor.