Советы с иерархией классов для игровых предметов - PullRequest
2 голосов
/ 22 октября 2008

Я пишу движок MUD и только что приступил к модели игровых объектов, которая должна быть расширяемой.

Мне нужна помощь главным образом потому, что то, что я сделал, кажется грязным, но я не могу придумать другое решение, которое работает лучше.

У меня есть класс с именем MudObject, а другой класс с именем Container. Контейнер может содержать несколько MudObjects, но сам по себе является MudObject, однако MudObject необходимо знать, в чем они содержатся. .

Так они выглядят примерно так:

public abstract class MudObject
{
    Container containedBy;
}

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

(обратите внимание, это всего лишь пример, и некоторые классификаторы и модификаторы доступа, свойства и тому подобное пропущены)

Теперь это само по себе кажется грязным, но давайте добавим еще кое-что к смеси:

Item - это MudObject, от которого будут унаследованы все визуальные предметы (например, оружие), однако некоторые из них также должны быть контейнерами (например, сундуки). Но в C # нет такого, как множественное наследование, так что все сводится к интерфейсам, лучший выбор - сделать контейнер интерфейсом (насколько я вижу). Однако была причина, по которой я не хотел, чтобы это было, то, что добавление MudObject к контейнеру заставит контейнер обновлять значение MudObject s .containedBy.

Есть идеи, которые могли бы сделать эту работу, или я попал в ловушку усложнения вещей?
Если так, что еще вы могли бы предложить?

Ответы [ 7 ]

4 голосов
/ 22 октября 2008

Я думаю, вы слишком усложняете. Если MudObjects может содержать другие MudObjects, то вам нужен единый базовый класс:

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

Это похоже на работу WinForms и ASP.NET. Многие элементы управления контейнерами являются элементами управления и могут содержать коллекцию элементов управления.

1 голос
/ 22 октября 2008

То, что вы просите, является разумным, и это Составной шаблон проектирования

1 голос
/ 22 октября 2008

То, что вы хотите, вполне разумно: оно ничем не отличается от элементов управления Windows, которые сами по себе могут быть контейнером других элементов управления.

Что вам нужно сделать, так это создать собственную реализацию List<MudObject>:

public class MudObjectList : List<MudObject>

, который реализует, помимо прочего, дополнительные функции:

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

Примечание: этот метод затеняет, а не переопределяет старую функциональность добавления

Таким образом, вы сразу же заполняете атрибут ContainedBy при добавлении. Конечно, подразумевается, что ваш ContainedBy может быть null, что означает, что это объект верхнего уровня.

Наконец, я не думаю, что есть необходимость создавать отдельные классы MudObject и Container, поскольку контейнер является неотъемлемой частью MudObject (ff использует автоматические свойства C # 3.0):

public abstract class MudObject
{
    MudObject ContainedBy { get; set; }
    MudObjectList Contains { get; set; }
}
0 голосов
/ 22 октября 2008

В соответствии с ответом Марка напишите пару классов, поддерживающих двунаправленные отношения родитель-ребенок под капотом.

0 голосов
/ 22 октября 2008

Идя с идеей композиции по наследству, ответ, который, кажется, исчез.

Возможно, я мог бы сделать что-то более подобное

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; }
}
0 голосов
/ 22 октября 2008

На самом деле, это плохая идея, чтобы что-то было одновременно и контейнером. Это нарушает ряд обязательных сценариев, которые делают предположения о IList; поэтому для сундука у меня может возникнуть соблазн иметь свойство Items на сундуке, который является коллекцией, но пусть сундук просто будет сундуком.

Впрочем, на вопрос, который задали ...

Я бы соблазнился сделать MudObject интерфейсом ... таким образом, вы можете использовать что-то вроде следующего, которое дает вам универсальный контейнер для любых конкретных объектов, а также автоматическое воспитание:

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;
    }
}
0 голосов
/ 22 октября 2008

Почему бы не сделать все контейнеры MudObjects? ... или, по крайней мере, иметь возможность содержать другие объекты с точки зрения кода вашего класса. Э.Г.

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

Затем вы можете установить какой-либо флаг на самом объекте, чтобы определить, способны ли игроки на самом деле помещать предметы в объект или из него, а не использовать тип объекта, чтобы выяснить это.

...