views:

413

answers:

4

My understanding of the Liskov substitution principle is that some property of the base class that is true or some implemented behaviour of the base class, should be true for the derived class as well.

I guess this would mean when a method is defined in a base class, it should never be overrided in the derived class - since then substituting the base class instead of the derived class would give different results. I guess this would also mean, having (non-pure) virtual methods is a bad thing?

I think I might have a wrong understanding of the principle. If I don't, I do not understand why is this principle good practice. Can someone explain this to me? Thanks

A: 

a short explanantion to (click on this link ->)Liskov substitution principle is that If you have a base class BASE and subclasses SUB1 and SUB2, the rest of your code should always refer to BASE and NOT SUB1 and SUB2.
that's it, simple, just don't refer to the subclasses

Omu
hey, this makes sense and I can see why that would be bad. But what it suggests looks very different from what the square and rectangle example commonly used to explain this principle suggest. It would be really helpful, if you can also explain this in terms of that example.
aip.cd.aish
@Omu - I think you mean that existing and future clients of BASE should keep on referring to BASE. Other parts of the code may very well need to refer to SUB1 and/or SUB2.
quamrana
@Omu: I agree with quamrana, I was initially confused by your answer - until I read the content of the link you provided.
aip.cd.aish
no other parts of the code should refer to the subs, for example if you have shape as base and rectangle, triangle subs, than you don't call triangle.draw, but you call figure.draw and figure will know how to draw himself, you don't need to tell him his a triangle
Omu
+3  A: 

No, it tells that you should be able to use derived class in the same way as its base. There're many ways you can override a method without breaking this. A simple example, GetHashCode() in C# is in base for ALL classes, and still ALL of them can be used as "object" to calculate the hash code. A classic example of breaking the rule, as far as I remember, is derivin Square from Rectangle, since Square can't have both Width and Height - because setting one would change another and thus it's no more conforms to Rectangle rules. You can, however, still have base Shape with .GetSize() since ALL shapes can do this - and thus any derived shape can be substituted and used as Shape.

queen3
hey, so you are saying that for any object in C# GetHashCode() can be called and you can be cast to object and still the GetHashCode() would return the same value. Or am I misunderstanding this? I am guessing the GetHashCode is implemented in the Object, and the derived objects don't override this. I don't see how this corresponds to the square/rectangle example. Can you please explain?
aip.cd.aish
I think that queen3 means that many classes will need to override `GetHashCode()`, but clients that call it (like hash tables) won't know that they're not calling the method defined for Object. Since the contract is still met, then no violation has taken place. However, with the Square/Rectangle example, clients might be able to tell what the class is of the instance they have been passed, or might fail in some way, so a violation *has* taken place.
quamrana
No, not same value, but some value that satisfies base class contract about this value. As for shapes and hashes, see, if for derived SomeObject you can only call GetHashCode() _after_ ToString() (for some reason), you break contract - now callers have to know if it's object or your SomeObject. Now you can't pass SomeObject to callers that expect object. Same with shapes - you can't pass Square to callers that expect Rectangle because they'll try to set Width and won't expect it changes Height. You can's substitute one class for another. Thus, LSP is broken.
queen3
+5  A: 

Subclasses overriding methods in the base class are totally allowed by the Liskov Substituion Principle.

This might be simplifying it too much, but I remember it as "a subclass should require nothing more and promise nothing less"

If a client is using a superclass ABC with a method something(int i), then the client should be able to substitute any subclass of ABC without problems. Instead of thinking about this in terms of variable types, perhaps think about it in terms of preconditions and postconditions.

If our something() method in the ABC base class above has a relaxed precondition that permits any integer, then all subclasses of ABC must also permit any integer. A subclass GreenABC is not allowed to add an additional precondition to the something() method that requires the parameter to be a positive integer. This would violate the Liskov Substitution Principle (i.e., requiring more). Thus if a client is using subclass BlueABC and passing negative integers to something() the client won't break if we need to switch to GreenABC.

In reverse, if the base ABC class something() method has a postcondition - such as guaranteeing it will never return a value of zero - then all subclasses must also obey that same postcondition or they violate the Liskov Substitution Principle (i.e., promising less).

I hope this helps.

dustmachine
hey, so if by substituing GreenABC for ABC and calling `something` returns a different value - is it in violation of the principle?
aip.cd.aish
If the value GreenABC returns is valid for the specification provided by ABC, then no, it is not in violation of the principle.
Grundlefleck
@Grundlefleck: sorry, if I don't seem to get it. So, a derived class cannot change the behaviour of the base class, only add on to it? i.e. I cannot override a method in a derived class to have a different implementation from the base class.
aip.cd.aish
No, a subclass can definitely have a different implementation, it just can't change the rules of how it behaves. I think we need to come up with a concrete example... let me think for a minute...
dustmachine
a `PizzaSharingService` interface or abstract class (base) has a `share(pizza,numSlices)` method that will return a list of slices. A precondition says that `numSlices` parameter can be any number 0-12 (inclusive, and if the number is zero then an empty list will be returned.) So the first team creates a `RoundPizzaSharingService` which cuts the pizza into triangular slices, obeying the rule regarding the number of slices being zero. The second team creates a `SquarePizzaSharingService`, but decides that if number of slices is zero it throws an exception or returns a null. This violates LSP.
dustmachine
@dustmachine, good example. IMO more examples == good... Say you have a `Rectangle` class that has `setWidth()` and `setHeight()`. A `Square` class subclasses `Rectangle` and overrides behaviour so that when a dimension is changed, the class takes care of keeping it a square shape. If someone has a reference to `Rectangle` and calls `setWidth(10); setHeight(5);` the contract for a rectangle states that the dimensions should now be 10x5, but if the reference is actually to a `Square` the dimensions will be modified to make it a square shape. The `Square` class is an example of violating LSP.
Grundlefleck
+1  A: 

I think that you're literally correct in the way you describe the principle and only overriding pure virtual, or abstract methods will ensure that you don't violate it.

However, if you look at the principle from a client's point of view, that is, a method that takes a reference to the base class. If this method cannot tell (and certainly does not attempt to and does not need to find out) the class of any instance that is passed in, then you are also not violating the principle. So it may not matter that you override a base class method (some sorts of decorators might do this, calling the base class method in the process).

If a client seems to need to find out the class of an instance passed in, then you're in for a maintenance nightmare, as you should really just be adding new classes as part of your maintenance effort, not modifying an existing routine. (see also OCP)

quamrana
So, in the commonly used square and rectangle example, it is not a violation for Square to inherit from Rectangle. It is a violation if the setWidth property of the rectangle, tried to identify it's real type (in case it was a Square passed to a function taking a Rectangle in) and tried to implement special logic for that case. Is this correct?
aip.cd.aish
@aip.cd.aish: Its not a violation if clients can't tell, and it is a violation if a client has to employ special logic to fix things. Its much better if methods of classes either don't care what their type is, or can safely assume that their type is the one on which the method was originally defined.
quamrana
@quamrana: Based on your reply to my comment to queen3's answer, a derived class cannot change the behaviour of the base class, only add on to it? i.e. I cannot override a method in a derived class to have a different implementation from the base class. Is this what the principle states (in addition to the part about a method should not try to identify what class it actually is)?
aip.cd.aish
@aip.cd.aish: You are not going to easily solve this if you think about this in terms of implementations. Better to think in terms of what the client expects. There are lots of things that you *can* do in an implementation that don't violate the expectations of a client, especially if a client has low or no expectations. Easy examples are the `GetHasCode()` or `Shape::GetArea()` in which its difficult for the client to detect faults. A Harder example is with Square and Rect where to conform you might have only Rectangle having `setHeight` and `setWidth` and only Square having `setSize`.
quamrana