Советы с иерархией классов для игровых предметов
Вопрос
Я пишу движок MUD и только что начал работу с объектной моделью игры, которая должна быть расширяемой.
Мне нужна помощь главным образом потому, что то, что я сделал, кажется грязным, но я не могу придумать другое решение, которое работает лучше.
У меня есть класс с именем MudObject
и другой класс с именем Container
. Контейнер может содержать несколько MudObjects
, но сам по себе является Item
, однако .containedBy
нужно знать, что они содержатся в . р>
Так они выглядят примерно так:
public abstract class MudObject
{
Container containedBy;
}
public abstract class Container : MudObject
{
List<MudObject> Contains;
}
(обратите внимание, это всего лишь пример, и некоторые классификаторы и модификаторы доступа, свойства и тому подобное пропущены)
Теперь только это само по себе кажется грязным, но давайте добавим что-то еще к миксу:
<=> - это <=>, от которого будут унаследованы все визуальные предметы (например, оружие), однако некоторые из них также должны быть контейнерами (например, сундуки). Но в C # нет такого, как множественное наследование, так что все сводится к интерфейсам, лучший выбор - сделать контейнер интерфейсом (насколько я вижу). Однако была причина, по которой я не хотел, чтобы это было, в этом случае добавление <=> к контейнеру приведет к тому, что контейнер обновит значение <=> s <=>.
Есть идеи, которые могли бы сделать эту работу, или я попал в ловушку усложнения вещей?
Если так, что еще вы могли бы предложить?
Решение
То, что вы просите, является разумным и представляет собой шаблон составного дизайна а> р>
Другие советы
Я думаю, что вы слишком усложняете. Если MudObjects может содержать другие MudObjects, то вам нужен один базовый класс:
public abstract class MudObject
{
MudObject containedBy; //technically Parent
List<MudObject> Contains; //children
}
Это похоже на работу WinForms и ASP.NET. Многие контейнерные элементы управления являются элементами управления и могут содержать коллекцию элементов управления.
То, что вы хотите, вполне разумно: оно ничем не отличается от элементов управления 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
, так как контейнер является неотъемлемой частью <=> (ff использует автоматические свойства C # 3.0):
public abstract class MudObject
{
MudObject ContainedBy { get; set; }
MudObjectList Contains { get; set; }
}
Почему бы не сделать все контейнеры MudObjects? ... или, по крайней мере, иметь возможность содержать другие объекты с точки зрения кода вашего класса. Например.
public abstract class MudObject
{
MudObject containedBy;
List<MudObject> contains;
}
Затем вы можете установить какой-либо флаг на самом объекте, чтобы определить, способны ли игроки на самом деле помещать предметы в объект или из него, а не использовать тип объекта, чтобы выяснить это.
На самом деле плохая идея, чтобы что-то было одновременно элементом и контейнером. Это нарушает ряд обязательных сценариев, которые делают предположения о 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;
}
}
Переходя к идее композиции поверх наследования, ответ, который, кажется, исчез.
Возможно, я мог бы сделать что-то более подобное
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; }
}
В дополнение к ответу Марка напишите пару классов, поддерживающих двунаправленные отношения между родителями и детьми.