Codementor Events

Liskov Substitution Principle

Published Nov 28, 2017
Liskov Substitution Principle

Originally posted on maksimivanov.com

In 1988, Barbara Liskov wrote something that now makes up the L in SOLID principles. Let’s dive in and learn what it is and how it relates to TDD.

Here is the original formulation: “If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”

Simply speaking: “Derived class objects must be substitutable for the base class objects. That means objects of the derived class must behave in a manner consistent with the promises made in the base class contract.”

Speaking even more simply: “Derived class objects should complement, not substitute, base class behavior.”

liskov

LSP can also be described as a counter-example of the Duck Test: "If it looks like a duck, quacks like a duck, but needs batteries – you probably have the wrong abstraction."

So, In The Real World

If you have some class Foo and a derived class SubFoo, then if you change all the notions of Foo class to SubFoo – the program execution shouldn't change, as SubFoo doesn't change the Foo class functionality, but only extends it.

Let's See The Example

Getting back to ducks. Let's describe a Duck. We have very low expectations of it. We only expect it to be able to quack and nothing else.

describe('Duck', function(){
  describe('#quack', function(){
    it('produces "Quack" sound', function(){
      const duck = new Duck();
      expect(duck.quack()).toEqual('Quack');
    });
  });
});

Fine, now let's define the basic duck.

class Duck{
  constructor(){
    // Duck initialization process
  }

  quack(){
    return 'Quack';
  }
}

We run the spec and it passes. Cool, now let's create a derived class MechanicalDuck. It should also be able to quack. The only difference is that it needs batteries to operate.

class MechanicalDuck extends Duck{
  constructor(battery=null){
    super();
    this._battery = battery;
  }

  quack(){
    if(!this._battery){
      throw 'Need battery to operate.';
    }
    return 'Quack';
  }
}

Now, according to LSP, we should be able to safely change instances of base class to instances of derived class. Let's change our spec a bit and try to use MechanicalDuck instead of Duck.

Uh-oh, test failed. MechanicalDuck needs a battery to quack. So MechanicalDuckhere is clearly not a duck. Even though it's interface might look similar, its behavior is totally different.

But What Would Be A Proper Subclass?

In our case it might be a FemaleDuck. Let's implement it.

class FemaleDuck extends Duck{
  constructor(){
    super();
    // Initialization of female stuff
    this._butt = new FemaleDuckButt();
  }

  layAnEgg(){
    const egg = this._butt.layAnEgg();
    return egg;
  } 
}

FemaleDuck will successfully pass the duck test, as we didn't change the behavior, but only extended it. Our duck can lay eggs, hurray!

Discover and read more posts from Maksim Ivanov
get started
post commentsBe the first to share your opinion
Nazreen Mohamad
6 years ago

Very succinct explanation. Is this approach to conceptualising extension of Classes widely accepted?

Show more replies