2010-02-22 11 views
31

De vez en cuando, me encuentro con un caso en el que quiero que una colección de clases posea una lógica similar. Por ejemplo, tal vez yo quiero tanto un Bird y un Airplane para poder Fly(). Si está pensando en un "patrón de estrategia", estaría de acuerdo, pero incluso con la estrategia, a veces es imposible evitar la duplicación del código.C# tiene clases e interfaces abstractas, ¿debería tener también "mixins"?

Por ejemplo, digamos que se aplica lo siguiente (y esto es muy similar a una situación real que encontré hace poco):

  1. Tanto Bird y Airplane necesitará tener una instancia de un objeto que implementa IFlyBehavior.
  2. Tanto Bird y Airplane necesidad de pedir la instancia IFlyBehavior a Fly() cuando OnReadyToFly() se llama.
  3. Tanto Bird y Airplane necesidad de pedir la instancia IFlyBehavior a Land() cuando OnReadyToLand() se llama.
  4. OnReadyToFly() y OnReadyToLand() son privadas.
  5. BirdAnimal hereda PeopleMover

Ahora, digamos que más tarde añadimos Moth, HotAirBalloon, y 16 otros objetos, y digamos que todos siguen el mismo patrón.

Ahora vamos a necesitar 20 copias del código siguiente:

private IFlyBehavior _flyBehavior; 

private void OnReadyToFly() 
{ 
    _flyBehavior.Fly(); 
} 

private void OnReadyToLand() 
{ 
    _flyBehavior.Land(); 
} 

Dos cosas que no me gustan de este:

  1. No es muy seco (la las mismas nueve líneas de código se repiten una y otra vez). Si descubrimos un error o añadido un BankRight()-IFlyBehavior, tendríamos que propagar los cambios a todas las 20 clases.

  2. No hay ninguna manera de hacer cumplir que los 20 clases implementan esta lógica interna repetitivo consistente. No podemos usar una interfaz porque las interfaces solo permiten miembros públicos. No podemos utilizar una clase base abstracta porque los objetos ya heredan las clases base, y C# no permite la herencia múltiple (e incluso si las clases ya no heredan las clases, que más tarde tal vez desee agregar un nuevo comportamiento que implementa, por ejemplo, ICrashable, por lo que una clase base abstracta no siempre será una solución viable).

¿Qué pasa si ...?

¿Qué pasa si C# tenía un nuevo constructo, dicen pattern o template o [complete su idea aquí], que funcionó como una interfaz, pero le ha permitido poner modificadores de acceso privados o protegidos en los miembros? Usted todavía necesita para proporcionar una implementación para cada clase, pero si su clase implementa el patrón PFlyable, tendría al menos tener una manera de hacer cumplir todas las clases que tenía el código repetitivo necesario llamar Fly() y Land().Y, con un IDE moderno como Visual Studio, podría generar automáticamente el código usando el comando "Implementar patrón".

Personalmente, creo que tendría más sentido ampliar el significado de la interfaz para cubrir cualquier contrato, ya sea interno (privado/protegido) o externo (público), pero sugerí agregar una construcción completamente nueva porque la gente parece ser muy inflexible sobre el significado de la palabra "interfaz", y no quería que la semántica se convirtiera en el foco de las respuestas de las personas.

Preguntas:

Independientemente de cómo lo llame, me gustaría saber si la función que estoy sugiriendo aquí tiene sentido. ¿Necesitamos alguna forma de manejar los casos en los que no podemos abstraer tanto código como nos gustaría, debido a la necesidad de modificadores de acceso restrictivos o por razones ajenas al control del programador?

actualización

Desde el comentario de AakashM, creo que ya hay un nombre para la función que estoy solicitando: una Mixin. Entonces, creo que mi pregunta se puede resumir a: "¿Debería C# permitir Mixins?"

+8

Creo que quiere estos: http://en.wikipedia.org/wiki/Mixin – AakashM

+0

@Aakash, creo que tiene razón. Es bueno tener una palabra para lo que estoy describiendo :) – devuxer

+0

@Aakash, si conviertes tu comentario en una respuesta, es muy probable que lo marque :) Tal vez añada su opinión sobre si mixins sería una buena característica para C# para agregar. Gracias. – devuxer

Respuesta

1

Usted acaba de describir aspect oriented programming.

Una popular implementación de AOP para C# parece ser PostSharp (El sitio principal parece estar fuera de servicio/no funciona, this es la página directa "Acerca de").


Para dar seguimiento a la observación: No estoy seguro de si PostSharp lo soporta, pero creo que está hablando this part de AOP:

de tipo Inter declaraciones proporcionan una forma de expresar preocupaciones transversales afectando la estructura de los módulos. tambien conocido como clases abiertas, esto permite a los programadores a declarar en una miembros lugar o padres de otra clase , por lo general con el fin de combinar todo el código relacionado con una preocupación en un aspecto.

+0

El OP quiere agregar un método OnReadyToFly() a su clase, que debe poder llamarse obvio en tiempo de compilación. AFAIK no puedes lograr esto con PostSharp. – jeroenh

+0

Entendí su pregunta como "una forma de agregar la delegación al comportamiento de todas las clases", lo que podría hacerse mediante un corte de punto. Sin embargo, leerlo nuevamente no estoy seguro de si entendí bien, no es mi lengua materna y no parece estar claro. Gracias por señalar eso. –

+0

Benjamin, creo que @jeroenh es correcto. La clave de mi pregunta es que 'OnReadyToFly()' y 'OnReadyToLand()' son 'privados'. Deben implementarse dentro de cada clase que sigue el patrón. Creo que, con C# como está hoy, esto no se puede hacer cumplir. Podría olvidar implementar uno o ambos de estos métodos y no recibiría ningún error o advertencia. Tampoco puedo "implementar automáticamente" estos métodos, como podría si fueran parte de una interfaz. – devuxer

2

Visual Studio ya ofrece esto en 'forma de hombre pobre' con fragmentos de código. Además, con las herramientas de refactorización a la ReSharper (y tal vez incluso con el soporte nativo de refactorización en Visual Studio), se logra un largo camino para garantizar la coherencia.

[EDIT: No pensé en los métodos de extensión, este enfoque te lleva aún más lejos (solo necesitas mantener _flyBehaviour como una variable privada). Esto hace que el resto de mi respuesta probablemente sea obsoleta ...]

Sin embargo; solo por el bien de la discusión: ¿cómo podría mejorarse esto? Aquí está mi sugerencia.

Uno podría imaginar algo como lo siguiente con el apoyo de una futura versión del compilador de C#:

// keyword 'pattern' marks the code as eligible for inclusion in other classes 
pattern WithFlyBehaviour 
{ 
    private IFlyBehavior_flyBehavior; 

    private void OnReadyToFly() 
    { 
     _flyBehavior.Fly(); 
    } 

    [patternmethod] 
    private void OnReadyToLand() 
    { 
     _flyBehavior.Land(); 
    }  
} 

que se podría utilizar entonces algo así como:

// probably the attribute syntax can not be reused here, but you get the point 
[UsePattern(FlyBehaviour)] 
class FlyingAnimal 
{ 
    public void SetReadyToFly(bool ready) 
    { 
     _readyToFly = ready; 
     if (ready) OnReadyToFly(); // OnReadyToFly() callable, although not explicitly present in FlyingAnimal 
    } 

} 

¿Sería ésta una mejora ? Probablemente. ¿Realmente vale la pena? Tal vez ...

+0

En cuanto a su edición: no creo que los métodos de extensión resuelvan nada. Son "estáticos" para uno y el comportamiento es un campo privado. No puede acceder al comportamiento en un método estático del tipo (y necesitaría varios de ellos, en la muestra para Animal y PeopleMover al menos). ¿Qué me estoy perdiendo? –

+0

Estoy de acuerdo con @benjamin sobre los métodos de extensión; no funcionarán en este caso, pero +1 para su propuesta sobre cómo se puede implementar un 'patrón'. Gracias. – devuxer

2

Cant utilizamos métodos de extensión para esta

public static void OnReadyToFly(this IFlyBehavior flyBehavior) 
    { 
      _flyBehavior.Fly() 
    } 

Esto imita la funcionalidad que queríamos (o Mixins)

+0

"Mixin" es la palabra que también se me ocurrió. Ruby los tiene. C++ puede hacerlas usando el Patrón de Plantilla Curiosamente Recurrente y la herencia múltiple. ¿C no hace herencia múltiple? – xtofl

+0

@xtofi, C# no es compatible con herencia de clases múltiples, pero admite herencia múltiple a nivel de interfaz .. – RameshVel

+2

Ignorando el error tipográfico en ambos nombres de variable: Esto no tiene en cuenta que las clases están usando composición: Your Bird no es IFlyBehavior, simplemente _has_ uno. Lo que lo lleva de vuelta al inicio: deberá agregar un método de delegación en cada clase (o exponer el comportamiento directamente). –

3

El problema que usted describe podría resolverse utilizando el patrón de Visitantes (todo se puede resolver usando el patrón Visitor, ¡así que ten cuidado!)

El patrón de visitante te permite mover la lógica de implementación hacia una nueva clase. De esta forma, no necesita una clase base, y un visitante trabaja extremadamente bien sobre diferentes árboles de herencia.

Para resumir:

  1. Nueva funcionalidad no necesita ser añadido a todos los diferentes tipos
  2. La llamada al visitante se puede tirar hasta la raíz de cada jerarquía de clases

Para una referencia, vea el Visitor pattern

+1

Seca, ¿estás seguro de eso? Este patrón parece muy útil, pero creo que todavía necesitaría implementar 'OnReadyToFly()' y 'OnReadyToLand()' para cada clase, ya que se declaran como 'privadas '. ¿Cómo hago cumplir que las implementaciones se proporcionan para ambos métodos? – devuxer

0

Usaré métodos de extensión para implementar el comportamiento como lo muestra el código.

  1. Que Bird y Plane objetos implementan una propiedad para IFlyBehavior objeto para un interfaz de IFlyer

    public interface IFlyer 
    { 
        public IFlyBehavior FlyBehavior 
    } 
    
    public Bird : IFlyer 
    { 
        public IFlyBehaviour FlyBehavior {get;set;} 
    } 
    
    public Airplane : IFlyer 
    { 
        public IFlyBehaviour FlyBehavior {get;set;} 
    } 
    
  2. crear una clase de extensión para IFlyer

    public IFlyerExtensions 
    { 
    
        public void OnReadyToFly(this IFlyer flyer) 
        { 
         flyer.FlyBehavior.Fly(); 
        } 
    
        public void OnReadyToLand(this IFlyer flyer) 
        { 
         flyer.FlyBehavior.Land(); 
        } 
    } 
    
+0

Olvidó usar la instancia IFlyer: * flyer * .FlyBehavior.Fly() – jeroenh

+0

Gracias jeroenh !! Haga la corrección – Sachin

+1

debe leer la pregunta completamente. métodos privados, amigo. privado. –

0

Podría conseguir este tipo de comportamiento al usar el nuevo ExpandoObject en .NET 4.0?

+0

Interesante, Nick. Todavía no tengo .NET 4.0, así que no puedo experimentar con él, pero parece que lo que hace es cambiar la "forma" de los objetos en tiempo de ejecución. Eso es genial (estoy deseando jugar con él), y muy bien podría funcionar, pero no es lo que estoy tratando de hacer aquí. Quiero decir, si fuera a seguir esta ruta, me pregunto si no estaría mejor solo haciendo algo de generación de código. Sé el código exacto que necesito en tiempo de ejecución, simplemente no quiero escribirlo (o copiarlo/pegarlo) cientos de veces. – devuxer

0

Scala traits fueron desarrollados para abordar este tipo de situaciones. También hay algunas investigaciones para incluir traits in C#.

ACTUALIZACIÓN: Creé mi propio experimento para tener roles in C#. Echar un vistazo.

Cuestiones relacionadas