Reducción de repeticiones en la clase que implementa interfaces a través de la composición.

10

Tengo una clase: Aes un compuesto de varias clases más pequeñas B, Cy D.

B, CY Dponer en práctica las interfaces IB, ICy IDrespectivamente.

Dado que Aadmite toda la funcionalidad de B, Ce D, Aimplementa IB, ICy IDtambién, pero desafortunadamente esto lleva a una gran cantidad de cambios de ruta en la implementación deA

Al igual que:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

¿Hay alguna manera de deshacerse de esta redirección repetitiva A? B, Cy Dtodos implementan funcionalidades diferentes pero comunes, y me gusta que Aimplemente IB, ICy IDporque significa que puedo pasar por Así mismo como una dependencia que coincide con esas interfaces, y no tengo que exponer los métodos auxiliares internos.

Nick Udell
fuente
¿Podría decir algo sobre por qué necesita una clase A para implementar IB, IC, ID? ¿Por qué no simplemente exponer BCD como público?
Esben Skov Pedersen
Creo que mi razón principal para preferir Aimplementar IB, ICy IDes que es más sensato escribir Person.Walk()en lugar de Person.WalkHelper.Walk(), por ejemplo.
Nick Udell

Respuestas:

7

Lo que estás buscando comúnmente se llama mixins . Lamentablemente, C # no los admite de forma nativa.

Hay pocas soluciones: una , dos , tres y muchas más.

De hecho, me gusta mucho el último. La idea de usar una clase parcial generada automáticamente para generar la plantilla repetitiva es probablemente la más cercana a una solución realmente buena:

[pMixins] es un complemento de Visual Studio que escanea una solución para clases parciales decoradas con atributos de pMixin. Al marcar su clase como parcial, [pMixins] puede crear un archivo de código subyacente y agregar miembros adicionales a su clase

Eufórico
fuente
2

Si bien no reduce la plantilla en el código en sí, Visual Studio 2015 ahora viene con una opción de refactorización para generar automáticamente la plantilla para usted.

Para usar esto, primero cree su interfaz IExamplee implementación Example. Luego cree su nueva clase Compositey hágala heredar IExample, pero no implemente la interfaz.

Agregue una propiedad o campo de tipo Examplea su Compositeclase, abra el menú de acciones rápidas en el token IExampleen su Compositearchivo de clase y seleccione "Implementar interfaz a través de 'Ejemplo'" donde "Ejemplo" en este caso es el nombre del campo o propiedad.

Debe tener en cuenta que si bien Visual Studio generará y redirigirá todos los métodos y propiedades definidos en la interfaz a esta clase auxiliar para usted, no redirigirá los eventos a partir del momento en que se publicó esta respuesta.

Nick Udell
fuente
1

Tu clase está haciendo demasiado, es por eso que tienes que implementar tanto repetitivo. Dices en los comentarios:

se siente más sensato escribir Person.Walk () en lugar de Person.WalkHelper.Walk ()

Estoy en desacuerdo. Es probable que el acto de caminar implique muchas reglas y no pertenezca a la Personclase: pertenece a una WalkingServiceclase con un walk(IWalkable)método donde la persona implementa IWalkable.

Siempre tenga en cuenta los principios SÓLIDOS cuando escriba código. Los dos que son más aplicables aquí son Separación de preocupaciones (extraer el código para caminar en una clase separada) y Segregación de interfaz (dividir interfaces en tareas / funciones / habilidades específicas). Parece que puede tener algo de I con sus múltiples interfaces, pero luego está deshaciendo todo su buen trabajo al hacer que una clase las implemente.

Stuart Leyland-Cole
fuente
En mi ejemplo, la funcionalidad se implementa en clases separadas, y tengo una clase compuesta de algunos de estos como una forma de agrupar el comportamiento requerido. Ninguna de las implementaciones reales existe en mi clase más grande, todo es manejado por los componentes más pequeños. Sin embargo, quizás tenga razón, y hacer públicas esas clases de ayuda para que puedan interactuar directamente con ellas es el camino a seguir.
Nick Udell
44
En cuanto a la idea de un walk(IWalkable)método, personalmente no me gusta porque los servicios de caminata se convierten en responsabilidad de la persona que llama y no de la Persona, y no se garantiza la coherencia. Cualquier persona que llame tendría que saber qué servicio utilizar para garantizar la coherencia, mientras que mantenerlo en la clase Persona significa que debe cambiarse manualmente para que el comportamiento de caminar difiera, al tiempo que permite la inversión de dependencia.
Nick Udell
0

Lo que estás buscando es herencia múltiple. Sin embargo, ni C # ni Java lo tienen.

Puede extender B desde A. Esto eliminará la necesidad de un campo privado para B, así como el pegamento de redireccionamiento. Pero solo puede hacer esto para uno de B, C o D.

Ahora, si puede hacer que C herede de B y D de C, entonces solo tiene que hacer que A extienda D ...;) - solo para que quede claro, no estoy alentando eso en este ejemplo ya que no hay indicación para ello.

En C ++, podría tener A heredar de B, C y D.

Pero la herencia múltiple hace que los programas sean más difíciles de razonar y tiene sus propios problemas; Además, a menudo hay una forma limpia de evitarlo. Aún así, a veces sin él, uno necesita hacer compensaciones de diseño entre repetitivo y cómo se expone el modelo de objeto.

Se siente un poco como si estuvieras tratando de mezclar composición y herencia. Si esto fuera C ++, podría usar una solución de herencia pura. Pero como no lo es, recomendaría abrazar la composición.

Erik Eidt
fuente
2
Puede tener herencia múltiple usando interfaces tanto en Java como en C #, tal como lo ha hecho el operador en su código de ejemplo.
Robert Harvey
1
Algunos pueden considerar implementar una interfaz igual que la herencia múltiple (como se dice en C ++). Otros no estarían de acuerdo, porque las interfaces no pueden presentar métodos de instancia heredables, que es lo que tendría si tuviera una herencia múltiple. En C #, no puede extender varias clases base, por lo tanto, no puede heredar implementaciones de métodos de instancia de varias clases, por lo que necesita toda la plantilla ...
Erik Eidt