简体   繁体   中英

c# - How to do MULTIPLE “mixins” correctly with Interfaces and/or Abstract Classes

I want to be able to define some objects and attach some "behaviors" to that object where the implementation is in the behavior not in the object. Rails-like: acts_as_taggable. As a concrete example, I want to say that Tasks can be Tagged. I don't want to have to code anything in Task about Tags beyond "enabling" the behavior via ... an interface? Therein lies my question. You can't put the implementation in an interface. I don't want to pollute my BaseObject [abstract?] class with all of the possible implementations.

Objects: Task, Note

Behaviors: Taggable, Emailable, Printable, Deferrable (

A Task may be tagged, emailed, printed, and deferred. A Note may be tagged, emailed, printed, but not deferred.

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));
    }

Implementation of ITaggable looks something like

{
    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;
    }
}

So what's the correct/best way to do this?

Jason

Well there are lots of ways depending on how you want to implement. In your example it might be better to do something along the lines of this:

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));
   }
}

This variation would allow you to always add more behaviors without having to do any drastic changes to your class structures either like implementing an interface on every class that you want to support the new behavior. You may want to come up with what your common object would be amongst your behavior class to make it easier to use for your scenario though. So you could use generics to allow you to do a more typed definition.

You might want to look at the Decorator pattern as well to give you even more ideas.

Unfortunately you're going to have to add some code to your classes, either the base class or the descendant.

You can, however, get away with the "taggable" class being a private member, and just passing on all method calls to that member object, but you still need to implement the interface.

Something like this:

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); }
}

I've implemented mixins in C# by using partial classes, and copying source code from a template file into partial class source files. If you accept a few limitations, it works quite well. I tried the T4 pre-processor to automatically generate the source files, but it was a little too cumbersome, so I've written a - quite minimalistic - tool to copy code from template files that are much easier to generate than T4 scripts. With a little care, you can even use Intellisense when writing your template.

For the tool, have a look at:

http://myxin.codeplex.com/

For an example, have a look at the source code for Streambolics.Gui at:

http://streambolicscore.codeplex.com/

要使用Visual Studio内置的T4代码生成技术创建mixins,您可以查看http://code.logos.com/blog/2009/04/creating_mixins_with_t4_in_visual_studio.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM