In a project I worked recently, noticed that some methods that were accepting a class that belongs to a hierarchy, had code similar to following:
public void Process(Animal animal) {
if(animal.GetType() == typeof(Dog)) {
Console.WriteLine("We have a dog");
}
else {
Console.WriteLine("not a dog");
}
}
Well, that struck me as a blatant violation of LSP, because now if you use a subclass of a dog, either in production code, unit testing mock, or as a part of dependency injection interceptor this code will not work the same way. I believe this particular scenario can be fixed easily, by changing condition to:
if (animal is Dog)
That got me thinking though:
Are there any other pitfalls to look for that can break LSP in client code?
UPDATE
Just to clarify, I am looking for possible pitfalls in code that uses the class in a hierarchy. I am aware and I am not looking for problems with badly contstructed hierarchies - (e.g. rectangle - square problem). I am trying to find out what to look for, to make sure that the code will support dynamic mocks, interceptors, decorated classes (e.g. LoggingDog) the same way as it would handle original class.
So far after going through the answers and links I can see that the only pitfall would be to use type of the class directly - that is use GetType()
method directly or through some other technology. Despite some comments here is
and as
operators, and even casting to base type will not break LSP in this case, as sub-classes will evaluate the same way the original class would.
Asking for help, clarification, or responding to other answers. Making statements based on opinion; back them up with references or personal experience.
The classic example of the inheritance technique causing problems is the circle-elipse problem (a.k.a the rectangle-square problem) which is a is a violation of the Liskov substitution principle. A good example here is that of a bird and a penguin; I will call this dove-penguin problem.
LSP violations symptomsDerivates that override a method of the base class method to give it completely new behaviour. Derivates that override a method of the superclass by an empty method. Derivates that document that certain methods inherited from the superclass should not be called by clients.
Simply put, the Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. In other words, what we want is to have the objects of our subclasses behaving the same way as the objects of our superclass.
If we ignore for the moment that there are indeed cases where you may need to cast to a specific type, in most cases the problem can be solved by changing the design:
public class Animal
{
public virtual void Process()
{
Console.WriteLine("Animal (not a dog)");
}
}
public class Dog : Animal
{
public override void Process()
{
Console.WriteLine("We have a dog");
}
}
By using this design, we avoid the need to cast in the code processing the animals:
var animal = ...; // maybe a Dog, maybe not
animal.Process();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With