Difference between the IS-A and Liskov Substitution Principle?

1.9k views Asked by At

I've just wondered if there is a difference between the IS-A (which is the UML terminology and OOP) and Liskov Substitution Principle (LSP)?

Actually, both are talking about the inheritance. So what is the main difference in practice?

3

There are 3 answers

2
GhostCat On BEST ANSWER

Both terms describe the same "concept" in the end.

The Liskov substitution principle tells you: an inheritance relation between to classes B (base) and C (child) is sound when each and any usage of some object of type B ... can be replaced with an object of type C.

This means: B defines an API and a public contract - and C has to uphold these properties, too!

And IS-A amounts to the same thing: that some object of C is also a B.

The "difference" is: the LSP gives you exact rules that you can check. Whereas IS-A is more of an "observation" or expression of intent. Like in: you express that you wish that class C IS-A B.

In other words: when you don't know how to properly use inheritance, IS-A doesn't help you writing correct code. Whereas the LSP clearly tells you that something like:

class Base { int foo(); }
class Child extends Base { @Override double foo(); }

is invalid. According to LSP, you can only widen method arguments, and restrict return values.

int iValue = someBase.foo();

can't be replaced with

int iValue = someChild.foo();

because the foo() method is widened in its result.

And a final thought: many people think that C IS-A B is the same as just writing down Child extends Base. Yes. But this only tells the compiler that C extends B. It doesn't mean that the methods you use in C will follow the LSP and thus turn C into a real valid descendant of B.

C IS-A B requires more than "C extends B". To be truly valid, LSP has to be uphold!

0
Owen Reynolds On

Is-A/Has-A is about whether to use inheritance. Is a laserCat a type of laser, or should it just have a field for the laser? LSP is a specific problem to watch for if you use inheritance in a certain way.

The good use of inheritance is having Animal a1; pointing to a Cat or Dog, using a1.speed() (*). LSP says Cat and Dog speed functions need to use the same units. Likewise, a1.setWeight for Cats can't allow negative weights, but Dogs changes them to 0. LSP is about consistency when you could be calling either function. It's actually pretty obvious, if you already know the Animal a1; trick, which is hard.

To contrast, suppose you have stand-alone Cats and Dogs. If for real the speeds are measured differently, it's fine to have Cats use metric and Dogs be in English. If Cats and Dogs inherit from Animal, but you never use the "a1 = Cat or Dog" trick, it's still fine. c1.speed() is for sure in metric, d1.speed() is clearly miles-per-hour. But if you have function animalRace(Animal a1), you have a problem.

A difference is also the tone. Is-a/has-a is plain advice for someone starting out. LSP is from a 30-year-old paper written for PhD's. The equations it uses are meant for graduate Com Sci majors. It uses Pre and Post conditions, which were common, well-known terms then. "Substitution" is a good math term, but today we'd just say "a base class that will be pointing to any subclass".

(*) In more detail: we have superclass Animal, and subclasses Cat and Dog. Animal has a stub speed function, and Cat and Dog each override it. a1.speed() looks up the correct one. A real example of this is an array of Animals, which really holds Cats and Dogs. Or a function with an Animal input, expecting a Cat or Dog.

(same *) Often the base class is abstract - we'll never create an Animal object. But the trick is the same if we have a Toaster superclass and a DeluxeToaster subclass. Anything taking a Toaster could be taking anything that "is-a" toaster.

0
jaco0646 On

Inheritance is a purely syntactic relationship, whereas Liskov Substitution is also a semantic relationship.

Syntax is easy: you learn how to declare one class as the child of another, and a compiler tells you whether or not you've written valid code. If the code compiles, you've created an inheritance relationship (IS-A).

Semantics are harder: they determine what the code means to clients. Semantic meaning often includes things like documentation. In popular OOP languages, a compiler cannot tell you whether code obeys or violates its intended meaning. That's where Liskov steps in.