When inheritance is not an inheritance

By Vladimir Khorikov

Nowadays, notion of composition over inheritance is quite widely accepted. It basically means that when designing a software, you should prefer composition to inheritance, although you could use either one.

But what if several classes do have some common attributes? Do you need to extract a base class for them?

When inheritance is not an inheritance

In OOP, inheritance stands for “is-a” relation. That is, a class A can be treated as a sub-class of a class B if A is a B in a way that makes sense for our particular domain.

That means that your decision of whether or not to create a subclass should be based on their semantics only. You could work on a bounded context in which two classes naturally relate to each other. But the same two classes can have a completely different meaning in another bounded context, and thus, cannot be related.

What you definitely shouldn’t do is make a decision based purely on the members classes have. Let’s look at an example:

public class NamedObject : Entity

{

    public string Name { get; set; }

}

 

public class Network : NamedObject

{

    // Other properties

}

 

public class Node : NamedObject

{

    // Other properties

}

Here, NamedObject is introduced to store the Name property as both Network and Node classes have it. That is a classic example of utility inheritance – a concept I encourage you completely avoid. The only reason to introduce a new class here is “normalizing” classes’ field set by pulling their common properties up to the base class.

Such inheritance is not a “real” inheritance because it doesn’t contain any domain knowledge. What does it mean to be a NamedObject in the domain you are working on? Does your domain really have such concept? When you talk to your domain experts, do you name Network and Node “NamedObject”? Do the domain experts use such term?

That is also true for interfaces:

public interface INamedObject

{

    string Name { get; set; }

}

 

public class Network : Entity, INamedObject

{

    public string Name { get; set; }

    // Other properties

}

 

public class Node : Entity, INamedObject

{

    public string Name { get; set; }

    // Other properties

}

Although there’s no additional class here, there still is a misleading concept of an entity having a role “NamedObject” which doesn’t make sense in terms of the domain.

That brings us to the following rule: if a class or interface is not a part of your ubiquitous language, you shouldn’t introduce it in your domain model. Although it might seem to be valuable in a utility sense, it most cases it is a sign of a poor design.

Every time you see such code, you should take a break and think of your model. There almost always is a way to refactor your code to get rid of the inconsistency between the code and the domain.

In the example above the reason why a new class was added was a code like this:

public string CreateMessage(Entity entity, string error)

{

    string entityName;

    if (entity is NamedObject)

    {

        entityName = ((NamedObject)entity).Name;

    }

    else

    {

        entityName = entity.Id.ToString();

    }

 

    return “Error in processing an entity “ + entityName + “: “ + error;

}

Such approach clearly breaks Open-Closed principle. We can refactor this code by introducing a new method GetName() in the Entity class:

public class Entity

{

    public virtual string GetName()

    {

        return Id.ToString();

    }

    // Other members

}

Or, we can reuse the ToString() method:

public class Entity

{

    public override string ToString()

    {

        return Id.ToString();

    }

    // Other members

}

The CreateMessage() method then turns into much simpler code:

public string CreateMessage(Entity entity, string error)

{

    return “Error in processing an entity “ + entity.GetName() + “: “ + error;

}

Not only did we remove the redundant class out of the domain model, we also drastically simplified the code using this model.

Summary

A mismatch between the code and the domain is a design smell. Always follow your domain model closely; if you face such issue, you should either introduce the missing term to your domain experts and see if they accept it or refactor your code.

LinkedInRedditTumblrBufferPocketShare