2009-02-21 12 views
15

¿Alguien puede pensar en cualquier situación para usar herencia múltiple? Cada caso se me ocurre puede ser resuelto por el operador método¿Un uso para herencia múltiple?

AnotherClass() { return this->something.anotherClass; } 
+0

¿Responde a esta pregunta?Si es así, acepta una respuesta. (Sugerencia: la respuesta de +12 podría ser la respuesta) –

Respuesta

21

La mayoría de los usos de la herencia múltiple a gran escala son para mixins. A modo de ejemplo:

class DraggableWindow : Window, Draggable { } 
class SkinnableWindow : Window, Skinnable { } 
class DraggableSkinnableWindow : Window, Draggable, Skinnable { } 

etc ...

En la mayoría de los casos, lo mejor es usar herencia múltiple que ver la herencia estrictamente 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 luego 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ícil) con herencia individual.

Por ejemplo, en muchos marcos de clase que ver algo como esto:

class Control { } 
class Window : Control { } 
class Textbox : Control { } 

Ahora, supongamos que usted desea un cuadro de texto con las características de la ventana? Como estar dragable, tener una barra de título, etc ... Se podría hacer algo como esto:

class WindowedTextbox : Control, IWindow, ITexbox { } 

En el modelo de herencia simple, no se puede heredar fácilmente de ambas ventanas y Cuadro de texto sin tener algunos problemas con el control duplicado objetos y otros tipos de problemas. También puede tratar un WindowedTextbox como una ventana, un cuadro de texto o un control.

También, a la dirección de su idioma .anotherClass(), .anotherClass() devuelve un objeto diferente, mientras que la herencia múltiple permite que el mismo objeto que se utilizará para diferentes propósitos.

12

encuentro herencia múltiple particularmente útil cuando se utiliza mixin clases.

Como se indica en Wikipedia:

En la programación lenguajes orientados a objetos, un mixin es una clase que proporciona una cierta funcionalidad para ser heredada por una subclase, pero no es destinado a estar solo .

Un ejemplo de cómo nuestro producto usa las clases de mezcla es para 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 clases normal, por lo que es muy común que estas clases para utilizar la herencia múltiple en este sentido.

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 que es más importante al hacer cualquier forma de herencia pública (simple o múltiple) es respetar la es una 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.

+0

Eso no es herencia múltiple. Se trata de usos de interfaces, pero C++ simplemente implementa de la misma manera. – erikkallen

+0

@Erik: solo es una interfaz si la clase mixin es completamente abstracta. En el ejemplo del código que di, Vertebrate :: BendSpine() tiene una implementación, aunque vacía. Todavía llamaría a esta herencia múltiple, incluso si no es el mejor ejemplo. – LeopardSkinPillBoxHat

+1

¿No tendría más sentido para los vertebrados ser una subclase de animal, y para el perro solo para la subclase Vertebrado? La última vez que revisé, todos los vertebrados eran animales. – Kibbee

2

Un caso trabajé en impresoras de etiquetas habilitadas para redes recientemente involucradas. 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.

2

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 aplicación o interfaz) se puede utilizar 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 Arrastra & Clase de descarte que maneja el arrastre. Si usted tiene un objeto que hace las dos cosas que 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{ 

} 
0

El siguiente ejemplo es sobre todo algo que veo a menudo en C++: a veces puede ser necesario debido a las clases de utilidad que usted necesita, pero debido a su diseño no se puede usar a través de la composición (al menos no de manera eficiente o sin hacer que el código sea aún más complicado que recurrir a la 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.

4

La mayoría de las personas usa la herencia múltiple en el contexto de la aplicación de interfaces múltiples en una clase. Este es el enfoque que aplican Java y C#, entre otros.

C++ le permite aplicar múltiples clases base 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 LeopardSkinPillBoxHat points out, está en mezclas. Un excelente ejemplo de esto es el Loki library, del libro de Andrei Alexandrescu Modern C++ Design. Él usa lo que él llama las clases de política que especifican el comportamiento o los requisitos de una clase dada a través de la herencia.

Otro uso más es uno que simplifica un enfoque modular que permite API-independence a través del uso de la delegación de clase hermana en la temida jerarquía de diamantes.

Los usos para MI son muchos. El potencial de abuso es aún mayor.

4

Java tiene interfaces. C++ no tiene.

Por lo tanto, la herencia múltiple se puede usar para emular la función de interfaz. Si eres un programador C# y Java, cada vez que utilice una clase que extiende una clase base, pero también lleva a cabo un par de interfaces, que son una especie de admitir la herencia múltiple puede ser útil en algunas situaciones.

+0

Estoy de acuerdo con esto :) Lo peor de aprender C++ y luego C# más tarde es la restricción de mixins. A veces me encuentro copiando y pegando implementando una interfaz mixin, que podría ser un código o diseño pobre para mi parte, pero no puedo pensar en otra cosa que hacer, y como programador odias copiar y pegar – cwap

+0

C++ emula interfaces 100% con clases abstractas. ¿De qué "función de interfaz" estás hablando? ¿Versión Java/C# de herencia múltiple para interfaces? –

+0

C++ no tiene una palabra clave 'interfaz', ya que no la necesita (tiene herencia múltiple). Si otros lenguajes como Java y C# tienen interfaces, entonces la herencia múltiple en C++ debe tener algunos casos de uso. – luiscubal

2

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.

+0

Teóricamente tienes razón, pero en la práctica es muy difícil hacerlo bien. Y el precio (constructores de herencia virtual) es bastante alto. –

+0

Ese precio solo se paga si necesita tener una herencia común heredada a través de varias ramas de herencia. En la práctica, esto rara vez sucede (y se convierte en un problema si la base repetida no tiene ningún estado). – Richard

+1

"La gente comenzará rápidamente a hacer cosas tontas como la clase LabelPrinter heredar de su clase TcpIpConnector en lugar de simplemente contenerlo". De Verdad? He escuchado esto mucho, pero nunca hay evidencia de que realmente suceda. Todo el material que he leído hace una clara distinción entre composición y herencia, y no veo cómo alguien podría confundir a los dos. –

1

Es cierto que la composición de una interfaz (Java o C# similares) además de reenvío para un ayudante puede emular muchos de los usos comunes de la herencia múltiple (en particular mixins). Sin embargo, esto se hace a costa de que se repita ese código de reenvío (y que viole DRY).

MI hace abrir una serie de áreas difíciles, y más recientemente algunos diseñadores del lenguaje han tomado decisiones que los peligros potenciales de MI superan los beneficios.

Del mismo modo se puede argumentar en contra de los genéricos (contenedores heterogéneos funcionan, los bucles pueden sustituirse por() recursión de 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 rica diversidad de lenguas y familias lingüísticas hace que sea más fácil para nosotros como desarrolladores para elegir buenas herramientas que resuelven el problema de la empresa en cuestión. Mi caja de herramientas contiene muchos elementos que rara vez uso, pero en esas ocasiones no quiero tratar todo como un clavo.

+1

Si una característica del lenguaje es potencialmente muy útil, pero normalmente será maltratada, probablemente estará presente en C++ y ausente en Java. Diferentes filosofías de diseño del lenguaje. –

+1

Sí. Java está diseñado para idiotas. –

1

Un ejemplo de cómo nuestro producto usa clases mixin es para 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 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.

De modo que 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) 

Yo también quiero ser capaz de asignar y el objeto clon 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 evitar esto mediante el uso de 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 reutilizo el comportamiento están ENREDADOS 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.

Toda la razón por la que esto es incluso un problema 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.

1

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 intento usarlo en mis aplicaciones, acabo lamentando la elección y, a menudo, arrancándola y reemplazándola con el código generado.

MI es uno más de esos "Úsalo si realmente lo necesitas, pero asegúrate de que realmente lo necesitas".

0

tuve que usar hoy, en realidad ...

Aquí fue mi situación - que tenía un modelo de dominio representado en la memoria donde un A contenía cero o más B (representado en una matriz), cada B tiene cero o más Cs, 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é). He escrito algo como esto (no había más que eso, y esto no es sintácticamente correcta, es sólo 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; 
} 

Entonces, para los tipos de dominio, lo hice:

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 qué piensan las personas de 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.

+1

Parece que estás haciendo esto más complejo de lo necesario. ¿Por qué no simplemente tener un "Nodo", que tiene un padre (que puede ser NULO) y varios hijos (que podrían estar vacíos). Luego, simplemente haga que A, B, C y D hereden de Nodo. –

+0

Quería poder decir que no puede hacer referencia al padre de una A ni agregarla a una D hija (porque A no tiene padre y D no tiene hijos). Con mi implementación, no se compilará si lo intentas. También mejoré esto con los genéricos para que no puedas agregar una C a una A, C o D, o si haces referencia al padre de una B, no necesitarás convertirla en una A (será una A ya a través de genéricos), etc. – paquetp