views:

127

answers:

5

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

A: 

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); }
}
Lasse V. Karlsen
+2  A: 

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.

Joshua Cauble
Interesting, I'll have to think about this one some more.
A: 

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/

Stephan Leclercq
Nice, I'm gonna chexx0r our your approach. ;-)
herzmeister der welten
Does look nicer than T4.
A: 

To create mixins using Visual Studio's built-in T4 code-generation technique, you may take a look at http://code.logos.com/blog/2009/04/creating_mixins_with_t4_in_visual_studio.html

herzmeister der welten
I've never been a fan of pre-processor based solutions. I did look at T4, and I'm not interested in that kind of solution.
A: 

Castle Aspect# can do actual mixins.

Aaronaught
I have to admit that I haven't wrapped my head around Aspect coding yet. Still need to do more research in that arena.