what do you think about this code? It's upgraded version of: Classes representing items in an RPG game

using System;
using System.Collections.Generic;

public class Item
{
    public string Name { get; protected set; }
    public int Cost { get; protected set; }

    public override string ToString()
    {
        return "Name: " + Name + "\nCost: " + Cost;
    }

    public Item(string name, int cost)
    {
        Name = name;
        Cost = cost;
    }
}

public abstract class SpecialItem : Item
{
    public virtual int GetSpecialProperty { get; }
    public override string ToString() { return base.ToString() + "\nDamages/ArmorPts/HungryRestorePts: " + GetSpecialProperty; }
    public SpecialItem(string name, int cost) : base(name, cost) { }
}

public abstract class ElementalsItem : SpecialItem
{
    public int Fire  { get; protected set; }
    public int Water { get; protected set; }
    public int Earth { get; protected set; }
    public int Wind  { get; protected set; }
    public override string ToString()
    {
        return base.ToString()
            + "\nFire Damages/Resistance: " + Fire
            + "\nWater Damages/Resistance: " + Water
            + "\nEarth Damages/Resistance: " + Earth
            + "\nWind Damages/Resistance: " + Wind;
    }
    protected ElementalsItem(string name, 
                             int cost,
                             int fire, 
                             int water,
                             int earth, 
                             int wind)
                             : base(name, cost)
    {
        Fire  = fire;
        Water = water;
        Earth = earth;
        Wind  = wind;
    }
}

public class Weapon : ElementalsItem
{
    public int Damages { get; private set; }
    public override int GetSpecialProperty { get { return Damages; }}
    public override string ToString() { return base.ToString(); }

    public Weapon(string name,
                  int cost,
                  int damages,
                  int fireDamages  = 0,
                  int waterDamages = 0, 
                  int earthDamages = 0,
                  int windDamages  = 0)
                  : base(name, cost, fireDamages, waterDamages, earthDamages, windDamages)

    {
        Damages = damages;
    }
}

public class Armor : ElementalsItem
{
    public int ArmorPts { get; private set; }

    public override int GetSpecialProperty { get { return ArmorPts; }}
    public override string ToString() { return base.ToString(); }

    public Armor(string name,
                 int cost,
                 int armorPts,
                 int fireResistance  = 0,
                 int waterResistance = 0,
                 int earthResistance = 0,
                 int windResistance  = 0) 
                 : base(name, cost, fireResistance, waterResistance, earthResistance, windResistance)
    {
        ArmorPts = armorPts;
    }
}

public class Food : SpecialItem
{
    public int HungryRestorePts { get; private set; }
    public override int GetSpecialProperty { get { return HungryRestorePts; }}
    public override string ToString() { return base.ToString(); }

    public Food(string name, int cost, int hungryRestorePts) : base(name, cost)
    {
        HungryRestorePts = hungryRestorePts;
    }
}

class Equipment
{
    public static void Test()
    {
        List<Item> items = new List<Item>();
        items.AddRange(new Item[] { new Item("Stone", 1),
                                    new Armor("Steel Helmet", 80, 10),
                                    new Weapon("Great Sword", 120, 80),
                                    new Armor("Storm Cuirass", 1000, 120, 0, 0, 0, 60),
                                    new Weapon("Fire Spear", 1400, 60, 0, 80, 0, 0),
                                    new Food("Apple", 5, 10) });
        foreach(Item i in items)
        {
            Console.WriteLine(i.ToString());
            Console.WriteLine();
        }
    }
}
share|improve this question
2  
Honestly, I find the whole design really over-thought and cumbersome without adding a lot of value. There is value in having a shallower inheritance tree, favoring composition over inheritance. But the existing answers cover pretty well improvements on the existing code w/o a complete rewrite. – Paul 16 hours ago
    
@Paul can you show me your way to write this? I will be grateful. :) – wdsa 13 hours ago

Edge cases are where such class hierarchies often fall short.

  1. An offhand short sword that adds to defense?
  2. A shield that does damage when you bash with it?
  3. A magic potion that not only makes you less hungry but also adds armor when you drink it?

How will those fit into your current hierarchy? One way to avoid those problems is to use aggregation instead of inheritance and treat each game object as a collection of arbitrary properties, that can be added to object or removed from it. You might want to look at how, for example, things are done in Unity, to get a better understanding of how things are actually designed in modern engines.

To be clear, there is nothing wrong with having game object hierarchies in small projects. It is the easiest approach and sometimes it is enough, especially if all you want is to write some code. I just wanted to give you some perspective.

share|improve this answer
    
Really good points there, things that should definitely be taken in account when making a medium size or bigger game, in which case composition over inheritance is the way to go usually. – denis 15 hours ago
    
Thanks for good tips, they will be useful in bigger projects. :) – wdsa 13 hours ago
  1. Abstract cant be instantiated so having a public constructor for them is pretty much useless as they are being created through derived classes and it makes more sense to have a protected constructor instead of a public one
    public abstract class SpecialItem : Item
    {
         public virtual int GetSpecialProperty { get; }
         public override string ToString() { return base.ToString() +"\nDamages/ArmorPts/HungryRestorePts: " +  GetSpecialProperty; }
         public SpecialItem(string name, int cost) : base(name, cost) { }
    }

Can become

public abstract class SpecialItem : Item
{
    public virtual int GetSpecialProperty { get; }
    public override string ToString() { return base.ToString() + "\nDamages/ArmorPts/HungryRestorePts: " + GetSpecialProperty; }
    protected SpecialItem(string name, int cost) : base(name, cost) { }
}
  1. Virtual function without any functionality should rather be marked as abstract.
public virtual int GetSpecialProperty { get; }

Can become :

public abstract int GetSpecialProperty { get; }
  1. Enviornement.NewLine instead of \n
    public override string ToString()
    {
        return base.ToString()
            + "\nFire Damages/Resistance: " + Fire
            + "\nWater Damages/Resistance: " + Water
            + "\nEarth Damages/Resistance: " + Earth
            + "\nWind Damages/Resistance: " + Wind;
    }

With C#6' interpolated strings Can become

        return $"{base.ToString()} {Environment.NewLine} " +
               $"Fire Damages/Resistance:  {Fire} {Environment.NewLine} " +
               $"Water Damages/Resistance:  {Water} {Environment.NewLine} " +
               $"Earth Damages/Resistance:  {Earth} {Environment.NewLine} " +
               $"Wind Damages/Resistance:  {Wind} {Environment.NewLine}";

4.Redundant overriding

You are overriding the ToString() in Armor, Weapon, Food but you are doing nothing but calling the base method, so you can just remove it.

public override string ToString() { return base.ToString(); }
  1. Readonly properties
public int HungryRestorePts { get; private set; }
public int ArmorPts { get; private set; }
public int HungryRestorePts { get; private set; }

Are rather readonly properties than properties with private sets, you are giving them value only in the constructor anyway with C#6 they can become :

 public int HungryRestorePts { get; }
 public int ArmorPts { get; }
 public int HungryRestorePts { get; }
  1. Expression bodied properties
public override int GetSpecialProperty { get { return Damages; } }

If you are using C# 6 it can be shorten to :

 public override int GetSpecialProperty => Damages;
  1. Long constructors

Your Armor & Weapon class have too much parameters making the initialization ugly also they seem like a immutable objects, you might want to apply the builder design pattern, you can check my answer on t3chb0t's question here, which if you configure properly can make your intialization a lot prettier + your objects will be immutable.

share|improve this answer
    
Afair, 3 and 5 are also C# 6 features, that are not available in earlier version. – Nikita B 17 hours ago
    
You are correct I will update the answer to reflect the changes. – denis 16 hours ago
1  
I disagree on your point on the read-only getter (public virtual int GetSpecialProperty { get; }). That establishes that the property is read-only but allows inheriting classes to override the details. That doesn't have to be abstract, as abstract requires them to do so when it may not be necessary. – Paul 16 hours ago
    
While that's true, it's being overwritten in every derived class with a specific variable from the derived class so I assumed it's meant to be overwritten in which case it's better to be abstract. – denis 16 hours ago
    
@NikitaB how do you know it's about an earlier version of C# before C# 6? – t3chb0t 16 hours ago

So, I think the biggest problem with your current solution is that it doesn't give us any context for what your trying to solve with this code, and what the classes are actually intended to do. As of right now, they're essentially data carriers with overridden ToString implementations which are not completely intention-revealing.

There's an excellent article in the comments of the linked post you had that covers a full discussion of these things here, and talks a lot about how the class structure should not be used necessarily for enforcing rules.

In any case, if it were me I would compose items from interfaces and a single base class as much as possible, eg

using System;

public class Item
{
    public Item(string name, int cost)
    {
        Name = name;
        Cost = cost;
    }
    public String Name { get; protected set;}
    public int Cost { get; protected set;}
}

public interface IWeapon
{
    int Damage {get; }
}

public interface IArmor
{
    int Resistance {get; }
}

public interface IFood
{
    int NutritionPoints { get; }
}

public interface IElement
{
    string Type { get; }
    int Strength { get; }
}

public class Spear: Item, IWeapon
{
    public Spear() : base("spear", 50)
    {
        Damage = 5; // or pass this in if you want variable damage spears
    }
    public int Damage { get; private set; }
}

public class PlateArmor: Item, IArmor
{
    public PlateArmor() : base("Plate Armor", 500)
    {
        Resistance = 50;
    }

    public int Resistance { get; private set; }
}

public class PotionOfProtection: Item, IArmor, IFood
{
    public PotionOfProtection(): base("Potion of Protection", 5000)
    {
        Resistance = 25;
        NutritionPoints = 50;
    }

    public int Resistance { get; private set; }
    public int NutritionPoints { get; private set; }
}

public class FireSpear: Item, IWeapon, IElement
{
    public FireSpear(): base("Fire Spear", 50000)
    {
        Damage = 7;
        Type = "Fire";
        Strength = 2; 
    }

    public int Damage { get; private set; }
    public string Type { get; private set; }
    public int Strength { get; private set; }
}

Behavior such as permissions and views (including the printing of things) would be handled with other classes responsible for doing so, for example a renderer class that will render information based on the type.

If an "elemental" based item can have more than one element associated with it, then I'd probably come up with another interface that covers that possibility, and in my design the 'strength' and 'type' attributes of an IElement would be evaluated by a resolver to multiply damage, resistance, etc, if the element is in play.

share|improve this answer

The Item class should be abstract because it's rather unlikely that you'll have an instance of it. Consequently the constructor needs to be protected. The two properties this class it provides should be private as you set them in the constructor and the derived classes call base(..) there's no need for their setters to be protected. Actually you can remove the setters if you are on C# 6.

The same rules apply to other classes derived from the Item or other items.


The GetSpecialProperty sounds a like a workaround or a property for everything and nothing. ToString suggests it has at least three purposes:

  • Damages
  • ArmorPts
  • HungryRestorePts

This is not a good idea. Try to implement those properties on the appropriated objects instead of having one that nobody knows what it actually means.

Side note: Don't abbreviate the names. Use Points and not Pts. It's the only

But I see have even more multipupose properties e.g. the ElementsItem has a Fire property and ToString says it stands for:

  • Damages
  • Resistance

You should really split this or give them proper names like FireResistance. How do you know which one is it actually?

I think you should create a new class Element and derive the Fire etc. from it so that you can use a dictionary or an array of elements.


\n

You should use the Environment.NewLine property or the StringBuilder.AppendLine method if there is no real reason for the \n.

share|improve this answer
1  
I don't agree with your opinion about about that Item should be abstract, because sometimes I will want to create objects that will have only basic properties like exactly name and cost, like stone or some jewellery. And I see that all of you are using Enviroment.NewLine, why "\n" is bad for this? :) – wdsa 13 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.