2009-08-17 13 views
35

Me he encontrado con un problema con la jerarquía de mi clase, en una aplicación WPF. Es uno de esos problemas en los que tiene dos árboles de herencia fusionándose, y no puede encontrar ninguna forma lógica de hacer que su herencia funcione sin problemas sin herencia múltiple. Me pregunto si alguien tiene ideas brillantes para que funcione este tipo de sistema, sin que sea imposible seguirlo o depurarlo.¿Cuáles son algunas buenas alternativas a la herencia múltiple en .NET?

Soy un ingeniero de bajo nivel, así que lo primero que pienso es: "¡Oh, escribiré algunas de estas clases en C++ nativo y las referiré a ellas desde el exterior! Entonces puedo tener toda mi vieja escuela OO diversión! " Por desgracia, esto no ayuda cuando se necesita para heredar de controles administrados ...

me permitirá mostrar un fragmento de mi diagrama actual clase proyectado:

____________________________________  _____________________________________ 
| CustomizableObject     | | System.Windows.Controls.UserControl | 
|____________________________________| |_____________________________________| 
| string XAMLHeader()    |      ▲ 
| string XAMLFooter()    |◄--┐     | 
| CustomizableObject LoadObject() | \     | 
| <Possible other implementations> | \     | 
|____________________________________|  \     | 
     ▲      ▲   \     | 
     |      |   \    | 
     |      |    \    | 
_________________ ______________________ \ _____________________ 
| SpriteAnimation | | SpriteAnimationFrame | └---| CustomizableControl | 
|_________________| |______________________|  |_____________________| 
                 ▲    ▲ 
                 |    | 
                 |    | 
                ________ _____________ 
               | Sprite | | SpriteFrame | 
               |________| |_____________| 

El problema es bastante claro: la separación de los árboles de objetos CustomizableObject y CustomizableControl --- y la inserción de UserControl en uno, pero no en ambos, de los árboles.

No tiene ningún sentido práctico mover la implementación de CustomizableObject a sus clases derivadas, ya que la implementación no varía según la clase. Además, sería realmente confuso tenerlo implementado muchas veces. Entonces realmente no quiero hacer de CustomizableObject una interfaz. La solución de interfaz no tiene ningún sentido para mí. (Las interfaces tienen nunca realmente tiene mucho sentido para mí, para ser honesto ...)

Así que vuelvo a decir, ¿alguien tiene alguna idea brillante? Este es un verdadero pepinillo. Me gustaría obtener más información sobre cómo hacer que las interfaces funcionen CON mi árbol de objetos, en lugar de hacerlo en contra de él. Estoy haciendo este motor de sprite simple usando WPF y C# como un ejercicio sólido, más que nada. Esto es tan fácil de resolver en C++, pero necesito resolver cómo resolver estos problemas en un entorno administrado, en lugar de lanzar las manos al aire y volver corriendo a Win32 cada vez que las cosas se ponen difíciles.

+19

+1 por dibujar todo esto :) – Kamarey

+1

Jaja, me alegro de que mi obra de arte pueda ser apreciada. – Giffyguy

+1

Esta pregunta no encaja bien en el formato Q & A de stackoverflows. "Cuáles son algunas buenas alternativas" ya supone que no hay una sola respuesta correcta. Hay muchas formas y opiniones diferentes. –

Respuesta

17

Un enfoque es utilizar métodos de extensión con una interfaz para proporcionar su implementación de "clase derivada", al igual que System.Linq.Consultable:

interface ICustomizableObject 
{ 
    string SomeProperty { get; } 
} 

public static class CustomizableObject 
{ 
    public static string GetXamlHeader(this ICustomizableObject obj) 
    { 
     return DoSomethingWith(obj.SomeProperty); 
    } 

    // etc 
} 

public class CustomizableControl : System.Windows.Controls.UserControl, ICustomizableObject 
{ 
    public string SomeProperty { get { return "Whatever"; } } 
} 

Uso: Como siempre y cuando tenga una directiva using a (o está en el mismo espacio de nombres) el espacio de nombres en el que se definen los métodos de extensión:

var cc = new CustomizableControl(); 
var header = cc.GetXamlHeader(); 
+0

Agregó un uso ejemplo para ti. Siempre que el compilador pueda encontrarlos, los métodos de extensión se ven y se sienten como métodos de instancia. No hay propiedades de extensión, lamentablemente, por lo tanto, cambiar el nombre del método para seguir convenciones de nombres de métodos. – dahlbyk

+4

Además, consulte este artículo en MSDN: http://msdn.microsoft.com/en-us/vcsharp/bb625996.aspx – dahlbyk

+0

¡Esta solución resuelve el problema perfectamente! Eso es realmente genial. La ventaja aquí es que puedo mantener la implementación en una única ubicación, en mi árbol de objetos, en lugar de distribuirla polimórficamente o con interfaces, por lo que funciona de maravilla con la mentalidad de control de WPF. Recomiendo esta solución a cualquiera que tenga problemas de herencia múltiple/interfaz en .NET – Giffyguy

1

Parece que tendrá que recurrir a interfaces y composición de objetos.

29

Tiene dos opciones aquí; usar interfaces, o usar composición. Honestamente, las interfaces son muy potentes, y después de leer esta línea

La solución de interfaz no tiene ningún sentido para mí. (Las interfaces nunca tuvieron mucho sentido para mí, para ser sincero ...)

Creo que deberías aprender a usarlas correctamente. Dicho esto, si simplemente existe alguna lógica que necesita una clase múltiple, pero no tiene sentido que estas clases hereden de la misma clase base, simplemente cree una clase para encapsular esa lógica y agregue una variable miembro de esa clase a sus clases que te están dando problemas De esta forma, todas las clases contienen la lógica pero pueden estar separadas en sus jerarquías de herencia. Si las clases deben implementar una (s) interfaz (s) común (s), entonces usa interfaces.

+0

Me gusta mucho la forma en que describes esto. Parece que probablemente tomaré la ruta de "composición". Esto complicará las cosas de manera molesta, pero menos que tener mis implementaciones por todos lados. (Jaja, soy tan terca ... Intento coraje para pedir SO directamente para que me ayuden con las interfaces, y TODAVÍA no puedo tragar mi orgullo y acepto todos los consejos sobre el poder de las interfaces). – Giffyguy

7

estoy mirando esto, y CustomizableObject está gritando para convertirse en una interfaz (y dado que cada tipo concreto es convertible a objeto, esa parte del nombre es redundante). El problema con el que se está metiendo es que no está seguro de cómo conservar una lógica básica que se compartirá o solo variará levemente según la implementación, y desea almacenar esta lógica en el árbol para que funcione polimórficamente (es esa una palabra?).

Puede lograrlo a través de los delegados. No estoy seguro exactamente lo que los miembros que están dando problemas, pero quizás algo más parecido a esto:

____________________________________  _____________________________________ 
| ICustomizable      | | System.Windows.Controls.UserControl | 
|         | |_____________________________________| 
| Func<string> XAMLHeader;   |      ▲ 
| Func<string> XAMLFooter   |◄--┐     | 
| ICustomizabl LoadObject() | \     | 
| <Possible other implementations> | \     | 
|____________________________________|  \     | 
     ▲      ▲   \     | 
     |      |   \    | 
     |      |    \    | 
_________________ ______________________ \ _____________________ 
| SpriteAnimation | | SpriteAnimationFrame | └---| CustomizableControl | 
|_________________| |______________________|  |_____________________| 
                 ▲    ▲ 
                 |    | 
                 |    | 
                ________ _____________ 
               | Sprite | | SpriteFrame | 
               |________| |_____________| 

Además, es probable que tenga alguna lógica que sea genuinamente estática que se siente realmente pertenece con su tipo CustomizableObject. Pero esto probablemente sea falso: construiste el tipo con la intención de usar ese tipo en una situación específica. Por ejemplo, desde el contexto, parece que creará estos controles y animaciones y los usará en Windows Form. Lo que hay que hacer es tener su propia forma que herede de la base System.Windows.Form, y este nuevo tipo de formulario debe saber sobre ICustomizableObject y cómo usarlo. Ahí es donde irá tu lógica estática.

Esto parece un poco incómodo, pero se ha demostrado que es preciso cuando decide cambiar los motores de presentación. ¿Qué sucede si transfieres este código a WPF o Silverlight? Es probable que necesiten usar su código de implementación de forma un poco diferente a lo que haría Windows Forms, y es probable que necesite cambiar sus implementaciones de CustomizableControl. Pero su lógica estática ahora está exactamente en el lugar correcto.

Finalmente, el método LoadObject() que está utilizando se destaca también en el lugar equivocado. Usted está diciendo que quiere que cada tipo de Personalizable proporcione un método al que pueda llamar que sepa cómo cargarse/construirse. Pero esto es realmente algo diferente. Es posible que desee otra interfaz llamada IConstructable<T> y que su tipo de letra personalizable implemente (IConstructable<ICustomizable>).

+0

+1 Hmmmm ... planteas un punto muy interesante. Realmente me gustaría que mis controles de sprite sean autónomos. Esto es WPF después de todo, así que probablemente no debería simplemente heredar desde el tipo de ventana base como lo hicimos en WinForms, pero podría aplicar fácilmente esta solución a, por ejemplo, mis controles SpriteLayer o SpriteView. – Giffyguy

+1

Debo decir que realmente me gusta el enfoque de delegado. De hecho, dediqué un tiempo a la codificación, tratando de ver cómo los delegados trabajarían para esto. Estaba planeando aceptar tu respuesta, una vez que la compilé y corrí, los métodos de extensión funcionaron mejor, y creo que más limpios.Sin embargo, esta es definitivamente una gran solución, digna de ser notada con seguridad. De hecho, estoy dispuesto a apostar que hay muchos casos en los que los delegados resolverían el problema incluso mejor que los métodos de extensión. – Giffyguy

1

Yo uso mixins en tal caso. mixins es un poderoso concepto utilizado en muchos idiomas para agregar funcionalidad a una clase en tiempo de ejecución. mixins son bien conocidos en muchos idiomas. El marco de remezcla es un marco que lleva la tecnología mixin a .NET.

La idea es simple: decora tu clase con un atributo de clase que representa un mixin. En tiempo de ejecución, esta funcionalidad se agregará a su clase. Entonces puedes emular herencia múltiple.

La solución a su problema podría ser hacer que CustomizableObject se mezcle. Necesitarás el marco de remezcla para esto.

[Usos (CustomizableObjectMixin)] CustomizableControl: control de usuario

Por favor, echa un vistazo a remix.codeplex.com para más detalles.

1

Creo que es uno de solución.

public interface IClassA 
{ 
    void Foo1(); 
    void Foo2(); 
} 

public interface IClassB 
{ 
    void Foo3(); 
    void Foo4(); 
} 

public class ClassA :IClassA 
{ 
    #region IClassA Members 

    public void Foo1() 
    { 
    } 

    public void Foo2() 
    { 
    } 

    #endregion 
} 

public class ClassB :IClassB 
{ 
    #region IClassB Members 

    public void Foo3() 
    { 
    } 

    public void Foo4() 
    { 
    } 

    #endregion 
} 

public class MultipleInheritance :IClassA, IClassB 
{ 
    private IClassA _classA; 
    private IClassB _classB; 

    public MultipleInheritance(IClassA classA, IClassB classB) 
    { 
     _classA = classA; 
     _classB = classB; 
    } 

    public void Foo1() 
    { 
     _classA.Foo1(); 
    } 

    public void Foo2() 
    { 
     _classA.Foo2(); 
     AddedBehavior1(); 
    } 

    public void Foo3() 
    { 
     _classB.Foo3(); 
     AddedBehavior2(); 
    } 

    public void Foo4() 
    { 
     _classB.Foo4(); 
    } 

    private void AddedBehavior1() 
    { 

    } 

    private void AddedBehavior2() 
    { 

    } 
} 

Clase MultipleInheritance agrega un nuevo comportamiento a dos objetos diferentes sin afectar el comportamiento de estos objetos. MultipleHealth tiene los mismos comportamientos que los objetos entregados (Hereda ambos comportamientos). Sory para mi inglés. Saludos.

+0

No veo eso como un patrón de decorador. –

+0

Creo que este es un enfoque válido y posible en algunos contextos, no veo por qué se votó negativamente. Obviamente, no es un enfoque general ya que los decoradores deben extender las clases existentes. Aunque podría usar el tipo de decorador directamente y agregarle algunos métodos completamente nuevos. Puedo ver _que_ no es el patrón decorador, pero puede funcionar como una variación de la composición. – julealgon

Cuestiones relacionadas