tags:

views:

134

answers:

7

Okay I need a little help, mainly because what I've done feels messy but I can't think of a another solution that fits better.

I'm writing a MUD engine and I've just started on the game object model, which needs to be extensible.

Now I have a class called MudObject, and another class called Container, A container can contain multiple MudObjects but is a MudObject itself, however MudObjects need to know what they are contained in

So they look something like this

public abstract class MudObject
{
    Container containedBy;
}

public abstract class Container : MudObject
{
    List<MudObject> Contains;
}

(please note these are just example and some qualifiers and access modifiers, properties and such are missed off)

Now just this in itself seems messy. But lets add something else to the mix

Item is a MudObject that all visual items (such as weapons) will be inherited from, however some of these need to be containers too (like chests). But theres no such as multiple inheritance in c#, So it comes down to interfaces, the best choice would be to make the container an interface (as far as I can see) However there was a reason I didn't want it to be, that being that adding an MudObject to a container will cause the container to update the MudObjects containedBy value.

So question is, any ideas that would make this work, or am I falling into the trap of making things too complicated? If so what else could you suggest?

+1  A: 

What you want is quite reasonable: it's no different from Windows form controls, which can itself be a container of other controls.

What you do need to do is create your own implementation of List<MudObject>:

public class MudObjectList : List<MudObject>

which implements, among other things, the add functionality:

public void new Add(MudObject obj)
{
    obj.ContainedBy = this;
    base.Add(obj);
}

Note: this method shadows, instead of overrides, the old Add functionality

In this way you immediately populate the ContainedBy attribute upon adding. Of course it's implied that your ContainedBy can be null, which means that it is the top level object.

Finally, I don't think there's a need to make separate MudObject and Container classes, since being a container looks intrinsic to a MudObject (the ff uses C# 3.0 automatic properties):

public abstract class MudObject
{
    MudObject ContainedBy { get; set; }
    MudObjectList Contains { get; set; }
}
Jon Limjap
List<T> does not expose a virtual Add that you can overrride
Marc Gravell
A: 

Why not make all MudObjects containers? ...or at least, have the ability to contain other objects, in terms of your class code. E.g.

public abstract class MudObject
{
    MudObject containedBy;
    List<MudObject> contains;
}

You can then set some sort of flag on the object itself to identify whether the players are able to actually put things into or out of the object themselves, rather than using the type of the object to figure it out.

Jon Grant
A: 

Actually, it is a bad idea for something to be both an item and a container. This breaks a number of binding scenarios that make assumptions about IList; so for a chest, I might be tempted to have an Items property on the chest that is the collection, but let the chest just be a chest.

However, for the question as asked...

I would be tempted to make the MudObject the interface... that way, you can use something like the following, which gives you a generic container of any concrete objects, along with automatic parenting:

public interface IMudObject
{
    IMudObject Container { get; set; }
    /* etc */
}

public class MudContainer<T> : Collection<T>, IMudObject
    where T : IMudObject
{

    public IMudObject Container { get; set; }

    protected override void ClearItems()
    {
        foreach (T item in this)
        {
            RemoveAsContainer(item);
        }
        base.ClearItems();
    }

    protected override void InsertItem(int index, T item)
    {
        SetAsContainer(item);
        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        RemoveAsContainer(this[index]);
        base.RemoveItem(index);            
    }
    protected override void SetItem(int index, T item)
    {
        RemoveAsContainer(this[index]);
        SetAsContainer(item);
        base.SetItem(index, item);
    }

    void RemoveAsContainer(T item)
    {
        if (item != null && ReferenceEquals(item.Container, this))
        {
            item.Container = null;
        }
    }
    void SetAsContainer(T item)
    {
        if (item.Container != null)
        {
            throw new InvalidOperationException("Already owned!");
        }
        item.Container = this;
    }
}
Marc Gravell
+3  A: 

I think you are overcomplicating. If MudObjects can contain other MudObjects, the single base class you need should be along these lines:

public abstract class MudObject
{    
    MudObject containedBy; //technically Parent
    List<MudObject> Contains; //children
}

This is similar to the way WinForms and ASP.NET works. Many container controls are both controls, and can contain a collection of subcontrols.

Jason Z
But not all MudObjects can contain other MudObjects
Sekhat
This design makes maintenance hard, as you need to maintain both parent and child relationships manually.
Marc Gravell
@Killersponge, if that is the case, you could set access to the Contains list as a property and add a property that allows an object to have sub-objects.
Jason Z
A: 

Going with the idea of composition over inheritance answer which seems to have vanished.

Perhaps I could do something more like this

public class Container<T> where T : MudObject
{
    List<T> Contains;
    MudObject containerOwner;

    public Container(MudObject owner)
    {
        containerOwner = owner;
    }
    // Other methods to handle parent association
}

public interface IMudContainer<T> where T : MudObject
{
    Container<T> Contains { get; }
}

public class MudObjectThatContainsStuff : IMudContainer
{
    public MudObjectThatContainsStuff()
    {
        Contains = new Container<MudObject>(this);
    }

    public Contains { get; }
}
Sekhat
A: 

Along the lines of Marc's answer, write a couple of classes that maintain a bidirectional parent-child relationship under the hood.

Justice
+1  A: 

What you're asking for is reasonable, and is the Composite Design Pattern

fizzer