tags:

views:

98

answers:

5

I have an abstract class BaseItem declared like this:

public abstract class BaseItem
{
    public BaseItem Parent { get; protected set; }
    public List<BaseItem> Children = new List<BaseItem>();

    public abstract string Function1();        
}

Basically, i'm trying to implement a design where each Item has a parent that will be of one specific type and children that will be of a different type.

For example, ItemA would have children all of ItemB type. Then ItemB would have a parent of ItemA type and children all of ItemC type. ItemC would have parent of ItemB and children of ItemD type.

I thought it would be neater to do this using generics to avoid unnecessary casts since i know what type the parent and children will be for each of my inherited classes. So i came up with something like this:

public abstract class AbstractBase
{
    public abstract string Function1();
}

public abstract class BaseItem<T1, T2> : AbstractBase
    where T1 : AbstractBase
    where T2 : AbstractBase
{
    public T1 Parent { get; protected set; }
    public List<T2> Children = new List<T2>();
}

public class ItemA : BaseItem<ItemA, ItemB>
{
}
public class ItemB : BaseItem<ItemA, ItemC>
{
}
public class ItemC : BaseItem<ItemB, ItemD>
{
}
public class ItemD : BaseItem<ItemC, ItemD>
{
}

So two things. 1. Is this a good design? Is there a simpler/better way of doing this? I don't really like using a second abstract base class just to be able to use generics. 2. If i do keep this, what's the best way of handling the ends? (i.e ItemA does not have a parent in my actual problem domain, but it needs a parent to compile. ItemD does not have children, but i need to give it something)

A: 

You might use two generic interfaces, ChildOf<T> and IList<T>. This will let you handle the end cases. Unfortunately since .NET doesn't have multiple inheritance, you won't be able to share the implementation.

Alternatively you could use the abstract class and a marker type (System.Void would be ideal, but I don't think it would compile) for the end cases, and check for it from the Parent/Children properties and throw an exception.

Ben Voigt
You can always use IoC/DI to inject default implementations of both interfaces
STW
A: 

It doesn't seem like too bad an idea, although to be honest I'd be tempted to keep the original solution and put up with the casting as it does add a fair bit of complexity for not that much benefit. (Although I must admit I try to replace casting with generics whenever I can).

If you did want to keep it, a couple of suggestions:

  • have AbstractBase as an interface
  • do something different for the ends (e.g. two extra BaseItems which take a parent and a child generic type).

Obviously, this second point only adds to the complexity, which is another reason I'm not sure it's worthwhile.

Grant Crofton
+1  A: 

If I understand you right, you are saying that ItemA never has a parent and ItemD never has any children, right? To be honest, I would probably just declare the separate classes with their own parent/children properties of the right type:

public abstract class AbstractBase
{
    public abstract string Function1();
}

public class ItemA : AbstractBase
{
    public List<ItemB> Children = new List<ItemB>();
}
public class ItemB : AbstractBase
{
    public ItemA Parent { get; protected set; }
    public List<ItemC> Children = new List<ItemC>();
}
public class ItemC : AbstractBase
{
    public ItemB Parent { get; protected set; }
    public List<ItemD> Children = new List<ItemD>();
}
public class ItemD : AbstractBase
{
    public ItemC Parent { get; protected set; }
}

The only thing you are repeating here is the Parent and Children property/field. All the other common functionality you can implement in the AbstractBase. (Unless, of course, that functionality needs access to the parents/children — but then you’re back to square one even in your solution.)

Timwi
Accepting your answer because in the end, to get generics to work is a lot of overhead no matter which way i do it, but for little benefit.
David A.
A: 

I would do it like this:

public interface IChildOf<T> {
    T Parent { get;  set; }
}

public interface IParent<T> {
    List<T> Children { get; set; }
}

//This class handles all of the cases that have both parent and children
public abstract class BaseItem<T1, T2> : IParent<T1>, IChildOf<T2>
{
    public List<T1> Children { get; set; }
    public T2 Parent { get; set; }
}

//This class handles the top level parent
public class ItemA : IParent<ItemB>
{
    public List<ItemB> Children { get;  set; }
}
public class ItemB : BaseItem<ItemC, ItemA>
{
}
public class ItemC : BaseItem<ItemD, ItemB>
{
}
//.... as many intermediates as you like.

//This class handles the bottom level items with no children
public class ItemD : IChildOf<ItemC>
{
    public ItemC Parent { get;  set; }
}
Daniel Dyson
A: 

I don't see a problem with doing things this way, since you appear to have a specific heirarchy that requires top-level items to be of type ItemA, 2nd-level items to be of type ItemB, etc.

To address the root and leaf nodes, I'd probably define separate classes that also derive from AbstractBase:

public abstract class RootItem<T2> : AbstractBase
    where T2 : AbstractBase
{
    public List<T2> Children = new List<T2>();
}

public abstract class LeafItem<T1> : AbstractBase
    where T1 : AbstractBase
{
    public T1 Parent { get; protected set; }
}

public class ItemA : RootItem<ItemB>
{
}
public class ItemB : BaseItem<ItemA, ItemC>
{
}
public class ItemC : BaseItem<ItemB, ItemD>
{
}
public class ItemD : LeafItem<ItemC>
{
}
Dr. Wily's Apprentice