Mac's avatar

MacReiben Web Zone

Christian Finel, software craftsman, DDs stuffs addict - @macreiben.

How to fail inheritance

Inheritance abuses bores me. I’m sorry to tell this, but IT teaching is often responsible.

The course usually say something like this:

When behaviour is used in several classes, then create a root class and put your code in there in a protected method. So all the children classes can reuse the behaviour.

So inheritance is solving the reusability problem.

I learnt to code this way, with a bunch of animals as examples.

It started with a cat

I know from my client that when a cat sees an intruder he miaows.

I composed my Cat. I read the SOLID principles. So I inject an implementation of yelling using an interface. That’s the D of SOLID principles.

  • The Dependency Inversion Principle: Depend on abstractions, not on concretions.

I created the CatYellingService:

public class YellingService : IYellingService {
  public void Yell() {
    Console.Writeline("Miaou miaou !!");
  }
}

This way if I want a bigger meow, I can switch the implementation. Great ! I wrote evolutive code !

Then …

I write the code to make the cat miaows when there is an intruder. So I wrote this:

public abstract class Animal {

  private IYellingService _service;

  protected Animal(IYellingService service) {
    _service = service;
  }

  protected void Yell() {
      _service.Yell();
  }
}

Then we have the Cat concrete:

public class Cat : Animal {
  public Cat(IYellingService yellingService) 
  :  base(yellingService) {

  }

  public void ShouldYellIfIntruder() {
    if(ThereIsAnIntruder()) {
      Yell();
    }
  }
}

To save time, I wrapped the call to YellingService.Yell() in Animals.Yell().

So next time I have to make another animal yell, I’ll call the Animal.Yell method.

And so I reduced development time for the future, hourah !

Then the dog came.

Today, my client/product owner says that “a dog should bark at the nearest window when there is an intruder”.

No problem! I just have to modify the IYellingService.Yell() and pass the nearest window to it.

Here is the new IYellingService:

public interface IYellingService {
  public void Yell(HouseWindow window);
}

public enum HouseWindow {
  LivingRoom,
  Kitchen,
  Bathroom,
  MyRoom
}

But, wait a minute … the product owner/client said that a dog should bark at the nearest window - not the cat. “Cats won’t miaow at windows!” said the client, “They just don’t care!”

OMG, What have I done..

At first, I modeled “When a cat sees an intruder, he yells” at first.

Doing so I created dependencies:

  • IYellingService type is known by the Animal class.
  • IYellingService type is known by all concrete classes inheriting from Animal class.
  • IYellingService.Yell method signature is known by the Animal class.
  • Animal.Yell method is known by all concrete classes inheriting from Animal class.
  • Animal concrete type is known by all classes inheriting from Animal class.

The consequence of my modeling choice is

  • When I change IYellingService.Yell method signature,
    • I change Animal class.
    • I change all Animal subclasses.

It’s just … everything.

Now, you see refactoring spectres coming out of shadows along with ghosts of despair … .

I’m stuck

Today When a dog sees an intruder, he sould bark at the nearest window.

Today, I’m stuck.

My design wasn’t bad at first. But let’s face it, it doesn’t give much room for structural changes.

Animal.Yell() is not overridable. Without the overridability, can’t even use the Open Close Principle to change Animal.Yell behaviour. No OCP then.

Beside this Animal.Yell calls a method that I could call from the Cat child class. Cat class knows IYellingService after all.

So Cat inherits from Animal to call a wrapper method on a method Cat class could call directly … .

So again, why Animal.Yell method even exists ?

“The client change his mind again ! That’s his fault !”.

Leave the client in peace. We wrote that poorly desgined code. That was our design choices. We are the only one to blame.

We thought we’ll save some time later by wrapping the call to a IYellingService.Yell service in a method (Animal.Yell()).

We love to tell ourself stories

Doing so we forgot that any code is a model of the reality even if it’s small wrapper method.

When I wrote mine, I bet that all my animals will yell the same way. I alone said “All animal yell” earlier.

And I lost. There is a discrepancy between my model and the reality. That’s me and me alone who created it.

The client changed his mind

Sure he did, so what ? Forget about it, it’s not important. It’s a constant in IT software.

“A software is done when it’s been put offline” - Liz Kheogh

Changes will occur as long as the software will be running.

Agile manifesto says:

  • Responding to change over following a plan

Software craftsman manifesto says:

  • Not only working software, but also well-crafted software.
  • Not only responding to change, but also steadily adding value

So, YES, the client will change his mind. And we have to turn it to working software.

So we MUST keep options to respond to changes.

Survival guide

Put aside all future consideration.

  • Model Now.
    • No more gambling
  • Ask for examples from po/testers/client
  • Move the model one step ahead.
  • Analyze dependencies based on the examples
    • Consider dependency change impact

Repeat :).

  • In the end, what is the benefit of the Animal.Yell method ?

A note of the TDD addict

I wanted to show this “anemic method wrapper in base class” pattern because I see it very often.

From a TDD point of view it also has serious flaws. You might be impacted by behaviours in base class constructor if you’re working on legacy code.

So you’ll have to mock behaviours that don’t even take part on the code you are testing.


Share