2017-08-04 2 views
0

Ich versuche, ein leistungsstarkes Spiel-Inventar-System zu implementieren. Ich habe diese abstrakte Basisklasse unterschiedliche Art der Gegenstände in der Bestands zu speichern, zum Beispiel Münzen, Taschenlampe, Messer etc ..Spiel-Inventar-System erstellen, ohne Casting zu abgeleiteten

public abstract class ObtainableItem 
{ 
    public string Name { get; private set; } 

    public ObtainableItem(string name) 
    { 
     Name = name; 
    } 
} 

Zum Beispiel habe ich eine Doorkey habe, die eine Tür öffnet. DoorKey hat eine Eigenschaft KeyCode, die zum Öffnen einer Tür verwendet wird.

public class DoorKey : ObtainableItem 
{ 
    public int KeyCode { get; private set; } 

    public DoorKey() : base("key") 
    { 
     KeyCode = 1234; 
    } 
} 

Alle ObtainableItem werden in Inventar gespeichert

public class Inventory 
{ 
    const int slotCount = 2; 
    ObtainableItem[] slots = new ObtainableItem[slotCount]; 

    public Inventory() 
    { 
     slots[0] = new DoorKey(); 
    } 
} 

Jetzt Benutzer vorstellen, zieht Doorkey aus seinem Inventar an einer Tür und löst Open-Methode

public class Door 
{ 
    public void Open(ObtainableItem key) 
    { 
     if (key is DoorKey) 
     { 
      DoorKey doorKey = (DoorKey)key; 
      if (doorKey.KeyCode == 1234) 
      { 
       // Open door 
      } 
     } 
     else 
     { 
      // "can't use this item on a door" 
     } 
    } 
} 

Wie Guss von ObtainableItem zu einem vermeiden Türschlüssel? Ich habe gelesen, dass die Verwendung von Casting eine schlechte Übung ist und auf ein schlechtes Code-Oop-Design hinweist. Idealerweise sollte eine Door-Klasse so aussehen. Gibt es ein Muster für mein Inventarsystem?

public class Door 
{ 
    public void Open(DoorKey key) 
    { 
     if (key.KeyCode == 1234) 
     { 
      // Open door 
     } 
    } 
} 
+2

Wo hast du gelesen * Casting ist schlechte Übung *, ich habe noch nie so etwas gelesen. –

+0

@ErikPhilips bezieht sich wahrscheinlich auf den Overhead von Boxen/Unboxing –

+1

@RandRandom ja, also dann ist es * Boxen/Unboxing kann Performance-Probleme verursachen *, die deutlich anders als * schlechte Praxis * ist. –

Antwort

1

Es gibt immer Ausnahmen, die für eine einfache Implementierung und Lesbarkeit gemacht werden können. Was Sie beschreiben, ist üblich, wenn nicht typisch.

Eine Alternative wäre, die Logik "Kontrolle" in der Klasse zu haben, die Door.Open aufruft. Dies könnte leicht mit einem Hauch von Reflexion erreicht werden:

public abstract class ObtainableItem 
{ 
    public string Name { get; private set; } 

    public ObtainableItem(string name) 
    { 
     Name = name; 
    } 
} 

public abstract class WorldItem 
{ 
} 

public interface IActsOn<in TWorldItem> 
    where TWorldItem : WorldItem 
{ 
    void ApplyTo(TWorldItem worldItem); 
} 

public class World 
{ 
    // If profiling shows that this is a performance issue, a cache keyed by tWorldItem, tInvItem 
    // should fix it. No expiry or invalidation should be needed. 
    private Action<ObtainableItem, WorldItem> GetApplyTo(Type tWorldItem, Type tInvItem) 
    { 
     var tActOn = typeof(IActsOn<>).MakeGenericType(tWorldItem); 
     if (!tActOn.IsAssignableFrom(tInvItem)) 
     { 
      return null; 
     } 
     var methodInfo = tActOn.GetMethod(nameof(IActsOn<WorldItem>.ApplyTo)); 

     return new Action<ObtainableItem, WorldItem>((invItem, worldItem) => 
     { 
      methodInfo.Invoke(invItem, new object[] { worldItem }); 
     }); 
    } 

    public bool IsDropTarget(WorldItem worldItem, ObtainableItem item) 
     => GetApplyTo(worldItem.GetType(), item.GetType()) != null; 

    public void ActOn(WorldItem worldItem, ObtainableItem item) 
    { 
     var actOn = GetApplyTo(worldItem.GetType(), item.GetType()); 
     if (actOn == null) 
     { 
      throw new InvalidOperationException(); 
     } 

     actOn(item, worldItem); 
    } 
} 

Während dies leicht die Implementierung von World erschwert, es vereinfacht die Umsetzung der verschiedenen Objekte:

class Door : WorldItem 
{ 
    public void Unlock(string bitting) 
    { 
     if (bitting == "1234") 
     { 
      Console.WriteLine("Door Opened"); 
     } 
     else 
     { 
      Console.WriteLine("Door could not unlock"); 
     } 
    } 
} 

class DoorKey : ObtainableItem, IActsOn<Door> 
{ 
    private readonly string Bitting; 

    public DoorKey(string bitting) 
     : base("Key") 
    { 
     this.Bitting = bitting; 
    } 

    public void ApplyTo(Door worldItem) 
    { 
     worldItem.Unlock(this.Bitting); 
    } 
} 

class RubberChicken : ObtainableItem 
{ 
    public RubberChicken() 
     : base("Rubber chicken") 
    { 
    } 
} 

Beispiel Nutzung:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var key1 = new DoorKey("1234"); 
     var key2 = new DoorKey("4321"); 
     var rubberChicken = new RubberChicken(); 

     var door = new Door(); 

     var world = new World(); 
     Debug.Assert(!world.IsDropTarget(door, rubberChicken)); 
     Debug.Assert(world.IsDropTarget(door, key1)); 

     world.ActOn(door, key2); 
     world.ActOn(door, key1); 

     Console.ReadLine(); 
    } 
} 
Verwandte Themen