Consigli con la gerarchia di classe per gli oggetti di gioco
Domanda
Sto scrivendo un motore MUD e ho appena iniziato il modello a oggetti di gioco, che deve essere estensibile.
Ho bisogno di aiuto principalmente perché quello che ho fatto sembra disordinato, ma non riesco a pensare a un'altra soluzione che funzioni meglio.
Ho una classe chiamata MudObject
e un'altra classe chiamata Container
, Un contenitore può contenere più MudObjects
, ma è un Item
stesso, tuttavia .containedBy
è necessario sapere in cosa sono contenuti .
Quindi sembrano simili a questo:
public abstract class MudObject
{
Container containedBy;
}
public abstract class Container : MudObject
{
List<MudObject> Contains;
}
(si prega di notare che questi sono solo esempi e alcuni qualificatori e modificatori di accesso, proprietà e simili vengono persi)
Ora, questo di per sé sembra disordinato, ma consente di aggiungere qualcos'altro al mix:
<=> è un <=> da cui verranno ereditati tutti gli oggetti visivi (come le armi), tuttavia alcuni di questi devono essere anche contenitori (come forzieri). Ma non esiste un'ereditarietà multipla in c #, quindi si tratta di interfacce, la scelta migliore sarebbe quella di rendere il contenitore un'interfaccia (per quanto posso vedere) Tuttavia c'era una ragione per cui non volevo che fosse, dato che aggiungendo un <=> a un contenitore, il contenitore aggiornerà il valore <=> s <=>.
Qualche idea che possa far funzionare questo, o sto cadendo nella trappola di rendere le cose troppo complicate?
Se sì, cos'altro potresti suggerire?
Soluzione
Quello che stai chiedendo è ragionevole, ed è il Composite Design Pattern
Altri suggerimenti
Penso che tu stia complicando troppo. Se MudObjects può contenere altri MudObjects, la singola classe base di cui hai bisogno dovrebbe essere lungo queste linee:
public abstract class MudObject
{
MudObject containedBy; //technically Parent
List<MudObject> Contains; //children
}
Questo è simile al modo in cui WinForms e ASP.NET funzionano. Molti controlli contenitore sono entrambi controlli e possono contenere una raccolta di sottocontrolli.
Quello che vuoi è abbastanza ragionevole: non è diverso dai controlli dei moduli di Windows, che possono essere essi stessi un contenitore di altri controlli.
Quello che devi fare è creare la tua implementazione di List<MudObject>
:
public class MudObjectList : List<MudObject>
che implementa, tra le altre cose, la funzionalità di aggiunta:
public void new Add(MudObject obj)
{
obj.ContainedBy = this;
base.Add(obj);
}
Nota: questo metodo ombreggia, anziché sostituire, la vecchia funzionalità Aggiungi
In questo modo si popola immediatamente l'attributo ContainedBy al momento dell'aggiunta. Naturalmente è implicito che ContainedBy può essere null
, il che significa che è l'oggetto di livello superiore.
Infine, non credo che sia necessario creare classi MudObject
e Container
separate, poiché essere un contenitore sembra intrinseco a un <=> (il file ff usa le proprietà automatiche di C # 3.0):
public abstract class MudObject
{
MudObject ContainedBy { get; set; }
MudObjectList Contains { get; set; }
}
Perché non creare tutti i contenitori MudObjects? ... o almeno, hanno la capacità di contenere altri oggetti, in termini di codice di classe. Per es.
public abstract class MudObject
{
MudObject containedBy;
List<MudObject> contains;
}
Puoi quindi impostare una sorta di bandiera sull'oggetto stesso per identificare se i giocatori sono effettivamente in grado di mettere le cose dentro o fuori l'oggetto stesso, piuttosto che usare il tipo di oggetto per capirlo.
In realtà, è una cattiva idea che qualcosa sia sia un elemento che un contenitore. Ciò interrompe una serie di scenari vincolanti che fanno ipotesi su IList; quindi per un forziere, potrei essere tentato di avere una proprietà Items su sul forziere che è la collezione, ma lascia che il forziere sia solo un baule.
Tuttavia, per la domanda come posta ...
Sarei tentato di rendere l'interfaccia MudObject ... in questo modo, puoi usare qualcosa come il seguente, che ti dà un contenitore generico di qualsiasi oggetto concreto, insieme alla genitorialità automatica:
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;
}
}
Accompagnando l'idea della composizione sulla risposta ereditaria che sembra essere svanita.
Forse potrei fare qualcosa di più simile a questo
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; }
}
Sulla falsariga della risposta di Marc, scrivi un paio di lezioni che mantengano una relazione bidirezionale genitore-figlio sotto il cofano.