Maksim Ivanov
React courseStart herePosts

Liskov Substitution Principle

November 10, 2017

In 1988 Barbara Liskov wrote something that now stands for L in SOLID principles. Let’s dive in and learn what is it and how does it relate 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.“_

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

So, In 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 dosen’t change the Foo class functionality, and only extends it.

Let’s See The Example

Getting back to ducks. Let’s describe a Duck. We have very low expectations on 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 lets 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 battery to quack. So MechanicalDuck here is clearly not a duck. Even though it’s interface might look similar, it’s 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!