Design Patterns – Composition Over Inheritance: Why Not Both?

compositiondesigndesign-patternsinheritanceobject-oriented

I have this out of context scenario, where what I think is good practices leaves me in a situation of both implementing an interface, and using composition to do the implementation.


Imagine the following:

I have a Character with Health and Mana, like so:

interface ICharacter
{
    Health Health { get; }
    Mana Mana { get; }
}

class Character : ICharacter
{
    public Health Health { get; private set; }
    public Mana Mana { get; private set; }

    public Character(Health health, Mana mana)
    {
        this.Health = health;
        this.Mana = mana;
    }
}

I then decide, that Health and Mana should be combined into one object, since they belong together.

interface IResource
{
    Health Health { get; }
    Mana Mana { get; }
}

I change my Character to

interface ICharacter
{
    IResource Resource { get; }
}

class Character : ICharacter
{
    public IResource Resource { get; private set; }

    public Character(IResource resource)
    {
        this.Resource = resource;
    }
}

However, so that clients of Character can access a character's health without saying character.Health.Current and "violating" the principle of least knowledge I want Character to provide this information. And to improve encapsulation I would no longer disclose how Character stores its Health and Mana, like so:

interface ICharacter : IResource
{
}

class Character : ICharacter
{
    private IResource resource;

    public Health Health
    {
        get
        {
            return resource.Health;
        }
    }

    public Mana Mana
    {
        get
        {
            return resource.Mana;
        }
    }

    public Character(IResource resource)
    {
        this.resource = resource;
    }
}

This gets my Character back to having Health and Mana which I like, but Character is now implementing the same interface it is am composed of.


In implementation, this looks quite a lot like the Decorator Pattern, but in intention, they have nothing in common.

Questions

  1. Is this pattern / antipattern of implementing the same interface that a class is composed of recognized, and if so, what is it called?

  2. Would you consider this good a idea over just having a property to IResource? – Why? / why not?

  3. Any situations to be wary off, where this is a definitive no-go?

SidenoteICharacter would certainly have more methods, but to shorten it and keeping it concise, I only included the relevant part here.

Best Answer

Composition over Inheritance, why not both?

The principle is correctly quoted as "prefer composition over inheritance". No one said you couldn't do both. Sometimes you have to do both. But given a choice, composition is usually the better one.

Your IResource reminds me of different patterns than the ones you mention. Parameter object makes constructing an object easier by bundling it's dependencies together.

Beware, taken to an extreme this smacks of a service locator which can be bad in some situations.

Respecting the Law of Demeter is a good thing if you understand it well. It is causing you a problem here but it's a good problem. It's forcing you to simplify access to health and mana until it's obvious that your Character isn't doing anything but aggregation.

What's missing is abstraction. Sure you promised Character would have more methods but as far as I see here Character isn't adding anything but unneeded indirection.

Right now Character looks like a brain dead value object (such as String). If that's what it's going to be then give it it's own copies of these values and flatten it out.

If Character is going to be a behavior object I'd expect Health to be hidden by encapsulation and interacted with using methods like Wound(int) and Die() not GetHealth(). This follows Tell, don't ask. A character's heath status is no one else's business. This is real encapsulation.

In either case the Character class should not be a brief stop on the way to dealing with character details. This should be THE place to resolve them. Don't make me dig past it.