inheritance only concern about what and how is accomplished, not what is promised. If you violate the promises of the base class, what will happen? is there any guarantee that makes you sure it's compatible? -even your compiler will not understand this mistake and you will face a bug in your codes. Such as:
class DoubleEndedQueue {
void insertFront(Node node){
// ...
// insert node infornt of queue
}
void insertEnd(Node node){
// ...
// insert a node at the end of queue
}
void deleteFront(Node node){
// ...
// delete the node infront of queue
}
void deleteEnd(Node node){
// ...
// delete the node at the end of queue
}
}
class Stack extends DoubleEndedQueue {
// ...
}
if the class wants to use inheritance with aim of code reuse, It may inherit a behavior that violates its principal, such as insertFront
. Let's also see another code example:
public class DataHashSet extends HashSet {
private int addCount = 0;
public function DataHashSet(Collection collection) {
super(collection);
}
public function DataHashSet(int initCapacity, float loadFactor) {
super(initCapacity, loadFactor);
}
public boolean function add(Object object) {
addCount++;
return super.add(object);
}
public boolean function addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
public int function getAddCount(Object object) {
return addCount;
}
}
I just reimplement HashSet
with DataHashSet
class in order to keep track of inserts. In fact, DataHashSet
inherit and is a subtype of HashSet
. we can instead of HashSet
just pass DataHashSet
(in java is possible). Also, I do override some of the methods of the base class. Is this legitimate from Liskov substitution principle? As I do not make any changes in the behavior of base class just add a track to insert actions, It seems perfectly legitimate. But, I will argue this is obviously a risky inheritance and a buggy code. First, we should see what exactly add
method do. add one unit to related property and call parent class method. There is a problem with that called yo-yo
. Look at addAll
method, first, it adds collection size to related property then call addAll
in the parent, but what exactly parent addAll
do? It will call add
method several times(loop over the collection), which add
will be called? the add
in the current class, so, the size of count will be added twice. once when you call addAll and second when parent class will call add method in the child class, that's why we call it yo-yo problem. And another example, imagine:
class A {
void foo(){
...
this.bar();
...
}
void bar(){
...
}
}
class B extends A {
//override bar
void bar(){
...
}
}
class C {
void bazz(){
B b = new B();
// which bar would be called?
B.foo();
}
}
As you see in bazz
method which bar
will be called? the second one the bar
in class B will be called. but, what is the problem here? the problem is foo
method in class A will not know anything about the override of bar
method in class B, Then your invariants may be violated. because foo may expect the only behavior of bar method that is in own class, not something is overridden. This problem is called fragile base-class problem.