2010-01-07 10 views
5

Quiero ser capaz de definir algunos objetos y adjuntar algunos "comportamientos" a ese objeto donde la implementación está en el comportamiento que no está en el objeto. Rails-like: acts_as_taggable. Como ejemplo concreto, quiero decir que las Tareas se pueden etiquetar. No quiero tener que codificar nada en Tarea sobre etiquetas más allá de "habilitar" el comportamiento a través de ... ¿una interfaz? Ahí radica mi pregunta. No puede poner la implementación en una interfaz. No quiero contaminar mi clase BaseObject [abstract?] Con todos de las posibles implementaciones.C# - Cómo hacer MÚLTIPLES "mixins" correctamente con interfaces y/o clases abstractas

Objetos: tarea, un

Comportamientos:. Taggable, enviarse por correo electrónico, imprimir, aplazable (

Una tarea puede ser etiquetado, enviado por correo electrónico, impreso, y se remitió Una nota puede ser etiquetada, enviado por correo electrónico, impreso , pero no diferido.

baseObject

public class BaseObject 
{ 
    Guid ID { get; set; } 
} 

tag.cs ​​

public class Tag : BaseObject 
{ 
    public Guid Id { get; set; } 
    public String Title { get; set; } 
} 

itaggable.cs

public interface ITaggable 
{ 
    void AddTag(Tag tag); 
    ... other Tag methods ... 
} 

task.cs

public class Task : BaseObject, ITaggable, IEmailable, IPrintable 
{ 
    Task specified functionality... nothing about "taggging" 
} 

note.cs

...

TagCollection.cs

public class TagCollection : List<Tag> 
{ 
    public string[] ToStringArray() 
    { 
     string[] s = new string[this.Count]; 
     for (int i = 0; i < this.Count; i++) 
      s[i] = this[i].TagName; 
     return s; 
    } 

    public override string ToString() 
    { 
     return String.Join(",", this.ToStringArray()); 
    } 

    public void Add(string tagName) 
    { 
     this.Add(new Tag(tagName)); 
    } 

Implementación de ITaggable se ve algo como

{ 
    private TagCollection _tc; 
    private TagCollection tc 
    { 
     get 
     { 
      if (null == _tc) 
      { 
       _tc = new TagCollection(); 
      } 
      return _tc; 
     } 
     set { _tc = value; } 
    } 

    public void AddTag(Tag tag) 
    { 
     tc.Add(tag); 
    } 

    public void AddTags(TagCollection tags) 
    { 
     tc.AddRange(tags); 
    } 

    public TagCollection GetTags() 
    { 
     return tc; 
    } 
} 

Entonces, ¿cuál es la mejor forma correcta/para hacer esto?

Jason

Respuesta

0

Desafortunadamente vamos a tener que añadir un poco de código a sus clases, ya sea la clase base o el descendiente.

Puede, sin embargo, salirse con la clase "taggable" siendo un miembro privado, y simplemente pasar todas las llamadas de método a ese objeto miembro, pero aún necesita implementar la interfaz.

Algo como esto:

interface ITaggable { 
    void AddTag(String tag); 
} 

class TaggableImplementation : ITaggable { 
    private Hashset<String> tags = new Hashset<String>(); 
    public void AddTag(String tag) { tags.Add(tag); } 
} 

class TaggableClass : ITaggable { 
    private ITaggable taggable = new TaggableImplementation(); 

    public void AddTag(String tag) { taggable.AddTag(tag); } 
} 
2

bien hay un montón de formas dependiendo de la forma en que desea implementar.En su ejemplo podría ser mejor que hacer algo en la línea de este:

public interface IBehavior 
{ 
    ... common behavior methods like maybe 
    bool Execute(object value) 
} 

public class Taggable : IBehavior 
{ 
    ... tag specific items 
    public bool Execute(object value) { ... } 
} 

public class Note 
{ 
    public List<IBehavior> Behaviors { get; set; } 
    public void ProcessNote() 
    { 
     this.Behaviors(d=>d.Execute(this)); 
    } 
} 

Esta variación se permitirá añadir siempre más comportamientos sin tener que hacer ningún cambio drástico a sus estructuras de clase, ya sea como la implementación de una interfaz en cada clase que quieras para apoyar el nuevo comportamiento. Si lo desea, puede pensar en cuál sería su objeto común entre su clase de comportamiento para que sea más fácil de usar para su escenario. De modo que podría usar genéricos para permitirle hacer una definición más tipada.

Es posible que también desee ver el patrón Decorator para darle aún más ideas.

+0

Interesante, tendré que pensar en esto más. – user166255

0

Implementé mixins en C# utilizando clases parciales y copiando el código fuente de un archivo de plantilla en archivos fuente de clase parcial. Si acepta algunas limitaciones, funciona bastante bien. Probé el preprocesador T4 para generar automáticamente los archivos fuente, pero era un poco engorroso, así que escribí una herramienta bastante minimalista para copiar código de los archivos de plantilla que son mucho más fáciles de generar que los de T4. Con un poco de cuidado, incluso puede usar Intellisense al escribir su plantilla.

Para la herramienta, echar un vistazo a:

http://myxin.codeplex.com/

Por ejemplo, tener un vistazo al código fuente para Streambolics.Gui en:

http://streambolicscore.codeplex.com/

+0

Bien, voy a chexx0r nuestro enfoque. ;-) – herzmeister

+0

Se ve mejor que T4. – user166255

Cuestiones relacionadas