Home > database >  OOP: How to "know" the object's type in this game design
OOP: How to "know" the object's type in this game design

Time:09-22

Let's image for a game, I have this scheme

Item is what all the game's Item based off

Some Item like Sword implements IEquipable

Some Item like Potion implements IConsumable

When I am giving out a quest reward, I add an Item to the character. When I am listing out the character's inventory, I go through their List of Item. At this time, I only know I have some Item but no longer know that the Item is Equipable or Consumable . Basically, the Interface is no longer known to me. What design can I do in this circumstance?

Let's be more specific.

When an Item is IConsumable, I would like them to "stack" on top of eachother, may be give a different glow to them and a right-click action will result in consuming that item

When an Item is IEquipable, The item will not stack inside the inventory, may be give a different glow to them and a right-click action will result in equipping that item

Should I do type-checking (If item is IEquipable then -> Do IEquipable-specific actions or access specific property). Or maybe there would be a different design altogether for this type of problem?

I am looking for answer that describe if this is not a problem or how I can avoid it if is a design problem (of needing to access to feature of more "refined" class while what we have at hand is the more generic class) - with example if possible

CodePudding user response:

In C# you can use the is keyword to check whether an object is an instance of a certain class/interface. Therefore you can do:

 foreach (var item in inventory)
 {
     if (item is IEquipable)
     {
         IEquipable equipable = item as IEquipable;
         //....
     }
     else if (item is IConsumable)
     {
         IConsumable consumable = item as IConsumable;
         //....
     }
 }

EDIT

If you want to use a structured pattern rather than an if else the following can help you. I personally think that in your case adds a lot of complexity that you can avoid.

let's start from the IItem interface. We'll let IConsumable and IEquipable inherit from it. We put the foundation of a Visitor pattern here that will be helpful later

 interface IItem {
      void Accept(IItemVisitor visitor);
 }
 interface IConsumable: IItem {}
 interface IEquipable: IItem {}

 interface IItemVisitor {
      void Visit(IConsumable consumable);
      void Visit(IEquipable equipable);
 }

 abstract class ConsumableBase: IConsumable
 {
      void Accept(IItemVisitor visitor) => visitor.Visit(this);
 }


 abstract class EquipableBase: IEquipable
 {
      void Accept(IItemVisitor visitor) => visitor.Visit(this);
 }

the visitor pattern is what will avoid that if/else of the previous solution.

Now I assume you have an InventoryManager class where you populate the Inventory given a list of items. It might look like the following:

 class InventoryManager : IItemVisitor
 {
       void PopulateInventory(IEnumerable<IItem> items)
       {
            foreach (var item in items)
            {
                 item.Accept(this);
            }
       }
       void Visit(IEquipable equipable)
       {
            //add equipable to the inventory
       }
       void Visit(IConsumable consumable)
       {
            //add consumable to the inventory
       } 
  }

This is the best pattern you can use when have to deal with different branches of the inheritance tree but you cannot solve with polymorphism directly. It has some drawbacks, for instance if you add a new interface that extends IItem you need to change all the visitor classes. It also adds some complexity to the code, so it's probably not worth the effort in some cases. Anyway, I think this is what you are looking for.

CodePudding user response:

As others have mentioned, the is keyword will do what you're describing, but I think it would be better to change your approach to the problem.

I'm going to ignore the "stacking" part of your question because I don't know what "stacking" means here--is it a visual stacking on-screen, or is it a logical combination of different items in the rules of the game? I think my answer will be clear enough using just the glow effect and consume/equip actions. If so, you should be able to connect the dots to implement stacking yourself.

The is and as keywords and the Linq OfType<T>() extension method are all simple ways to do the type-checking you described. They allow some sort of central game manager or player class (I'll just say "game manager" from now on) to ask each Item what its type is and then make decisions based on the response. The approach will work for simple systems, but might become difficult to maintain as the number of Item types grows--the game manager needs to know about the quirks of every Item type.

Instead, consider adding new properties or methods to Item: perhaps a GlowEffect property, and Use method. These methods would allow the game manager to ask the Item what to do or notify the Item that something has happened in the game. The game manager no longer needs to know about every possible Item type or its behavior--the behavior is encapsulated in the Item itself. You might not even need the IEquippable and IConsumable interfaces anymore.

I would characterize this as a "strategy" design pattern. Each Item implementation contains strategies for how to display or interact with a specific in-game resource or tool.

public abstract class Item : Item
{
    public abstract GlowEffect GlowEffect { get; }

    // A reference to the current Player allows the "Use"
    // method to affect the active player.
    public abstract void Use(Player player);
}
public class Sword
{
    public override GlowEffect GlowEffect { get { return new GreenGlowEffect(); } }

    public override void Use(Player player)
    {
        player.Equip(this);
    }
}
  • Related