Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enforcing type safety of inherited members in inherited classes

I’m trying to adhere to good OO design principles and design patterns and such. So while developing this C# application of mine I can often find multiple solutions to design and architecture issues, I always want to find and implement the more “canonical” one in the hopes of building highly maintainable and flexible software and also become a better OO programmer.

So suppose I have these two abstract classes: Character and Weapon.

abstract class Weapon
{
    public string Name { get; set; }
}

abstract class Character
{
    public Weapon weapon { get; set; }
}

Derived classes might be Sword and Staff from Weapon, and Warrior and Mage from Character, etc. (this is all hypothetical, not related to my actual software!).

Every Character has a Weapon, but for every implementation of Character I know what implementation of Weapon it will have. For instance, I know (and I want to enforce!) that at runtime every instance of Warrior will have a Weapon of type Sword. Of course I could do this:

class Sword : Weapon
{
    public void Draw() { }
}

class Warrior : Character
{
    public Warrior()
    {
        weapon = new Sword();
    }
}

But this way, every time I want to use my Weapon object properly inside a Warrior, I have to perform a cast, which I believe to be a not so great of a practice. Also I have no means to prevent myself from messing up, that is, there is no type safety!

An ideal solution would be to be able to override the Weapon weapon property in the Warrior class with a Sword weapon property, that way I have type safety and if the user uses my Warrior as a Character, he can still use my Sword as a Weapon. Sadly, it doesn’t seem like C# supports this kind of construct.

So here are my questions: is this some kind of classical OO problem, with a name and a well-documented solution? In that case I would very much like to know the name of the problem and the solutions. Some links to good reading material would be very helpful! If not, what kind of class design would you propose in order to maintain functionality and enforce type safety in an elegant and idiomatic way?

Thanks for reading!

like image 893
WHermann Avatar asked Feb 14 '15 23:02

WHermann


People also ask

How can you prevent member of class from being inherited?

How to Prevent Inheritance. In C# you can use the sealed keyword in order to prevent a class from being inherited. Also in VB.NET you can use the NotInheritable keyword to prevent accidental inheritance of the class.

What are the classes of inheritance?

Inheritance allows us to define a class that inherits all the methods and properties from another class. Parent class is the class being inherited from, also called base class. Child class is the class that inherits from another class, also called derived class.

What happens when a class is inherited?

Inheritance is a feature or a process in which, new classes are created from the existing classes. The new class created is called “derived class” or “child class” and the existing class is known as the “base class” or “parent class”. The derived class now is said to be inherited from the base class.

How do you use composition instead of inheritance?

To get the higher design flexibility, the design principle says that composition should be favored over inheritance. Inheritance should only be used when subclass 'is a' superclass. Don't use inheritance to get code reuse. If there is no 'is a' relationship, then use composition for code reuse.


2 Answers

UPDATE: This question was the inspiration for a series of articles on my blog. Thanks for the interesting question!

is this some kind of classical OO problem, with a name and a well-documented solution?

The fundamental problem you're running into here is that it is difficult in an OO system to add a restriction to a subclass. Saying "a character can use a weapon" and then "a warrior is a kind of character that can only use a sword" is hard to model in an OO system.

If you're interested in the theory behind this, you'll want to look for the "Liskov Substitution Principle". (And also any discussion of why "Square" is not a subtype of "Rectangle" and vice versa.)

There are however ways to do it. Here's one.

interface ICharacter
{
    public IWeapon Weapon { get; }
}
interface IWeapon {  }
class Sword : IWeapon { }
class Warrior : ICharacter 
{
    IWeapon ICharacter.Weapon { get { return this.Weapon; } }
    public Sword Weapon { get; private set; }
}

Now the public surface area of Warrior has a Weapon that is always a Sword, but anyone using the ICharacter interface sees a Weapon property that is of type IWeapon.

The key here is to move away from the "is a special kind of" relationship and move towards a "can fulfill the contract of" relationship. Rather than saying "a warrior is a special kind of character", say "a warrior is a thing that can be used when a character is required".

All this said, you are embarking upon a whole world of fascinating problems where you'll discover just how bad at representing these sorts of things OO languages really are. You'll quickly find yourself deep in the double-dispatch problem and learning how to solve it with the Visitor pattern, once you start asking more complicated questions like "what happens when a warrior uses a sharp sword to attack an orc wearing leather armor?" and discover that you have four class hierarchies -- character, weapon, monster, armor -- all of which have subclasses that influence the outcome. Virtual methods only allow you to dispatch on one runtime type, but you'll find that you need two, three, four or more levels of runtime type dispatch.

It gets to be a mess. Consider not trying to capture too much in the type system.

like image 56
Eric Lippert Avatar answered Sep 21 '22 01:09

Eric Lippert


Just use generics type constraint ! Here's an example

public abstract class Weapon
{
    public string Name { get; set; }
}

public interface ICharacter
{
    Weapon GetWeapon();
}

public interface ICharacter<out TWeapon> : ICharacter
    where TWeapon : Weapon
{
    TWeapon Weapon { get; } // no set allowed here since TWeapon must be covariant
}

public abstract class Character<TWeapon> : ICharacter<TWeapon>
    where TWeapon : Weapon
{
    public TWeapon Weapon { get; set; }
    public abstract void Fight();
    public Weapon GetWeapon() { return this.Weapon; }
}

public class Sword : Weapon
{
    public void DoSomethingWithSword()
    {
    }
}

public class Warrior : Character<Sword>
{
    public override void Fight()
    {
        this.Weapon.DoSomethingWithSword();
    }
}
like image 30
rducom Avatar answered Sep 20 '22 01:09

rducom