Liskov Substitution: keep promises of the base type
A subtype must honor the behavior promised by its base type. Model capabilities explicitly when they vary.
Watch it animatedStep through it, with the running line highlighted. Free with an account.The idea
Penguin can replace Bird wherever the Bird contract is expected because both provide the promised eating behavior. Flying is a separate capability, so code that needs flight accepts IFlyable rather than assuming every Bird can fly.
Code
class Bird {
public:
virtual string eat() const = 0;
virtual ~Bird() = default;
};
class Flyable {
public:
virtual string fly() const = 0;
virtual ~Flyable() = default;
};
class Sparrow : public Bird, public Flyable {
public:
string eat() const override { return "sparrow eats"; }
string fly() const override { return "sparrow flies"; }
};
class Penguin : public Bird {
public:
string eat() const override { return "penguin eats"; }
};
string describe(const Bird& bird) { return bird.eat(); }
int main() {
Penguin penguin; const Bird& bird = penguin;
cout << describe(bird) << "\n";
}from abc import ABC, abstractmethod
from typing import Protocol
class Bird(ABC):
@abstractmethod
def eat(self) -> str: ...
class Flyable(Protocol):
def fly(self) -> str: ...
class Sparrow(Bird):
def eat(self) -> str: return "sparrow eats"
def fly(self) -> str: return "sparrow flies"
class Penguin(Bird):
def eat(self) -> str: return "penguin eats"
def describe(bird: Bird) -> str: return bird.eat()
bird: Bird = Penguin()
print(describe(bird))abstract class Bird {
public abstract string Eat();
}
interface IFlyable {
string Fly();
}
class Sparrow : Bird, IFlyable {
public override string Eat() => "sparrow eats";
public string Fly() => "sparrow flies";
}
class Penguin : Bird {
public override string Eat() => "penguin eats";
}
static string Describe(Bird bird) => bird.Eat();
Bird bird = new Penguin();
Console.WriteLine(Describe(bird));Tips
- A subtype should not surprise callers by weakening preconditions, breaking postconditions, or violating invariants promised by the base type.
- Inheritance should model a true behavioral “is-a” relationship. If a capability is optional, a focused interface can express it better.
In the .NET library
The name honors Barbara Liskov. This example avoids the common Bird/Fly trap by keeping “can eat as a bird” separate from “can fly.”