24

Overriding a method originally defined in the super class, by definition means this method will do different things when invoked on an object of the base class or an object of the subclass.

So does this mean that overriding methods always means violating LSP? Or are there cases when it doesn't?

gnat
  • 20,543
  • 29
  • 115
  • 306
Aviv Cohn
  • 21,538

5 Answers5

37

LSP forbids to violate the contracts of a supertype in a subtype, it does not forbid to change the behaviour of any method (within the bounds of that contract).

For example, lets assume you have a superclass Report, associated with a certain object Foo, with a method ToString(), and subtypes HTMLReport, XMLReport and TextReport. Lets further assume Report is not abstract and the default implementation of ToString() is to return the empty string. Now you define a contract in the following manner:

  • ToString() shall deliver a string with a textual representation of Foo in a certain text format (and the empty string if the format is not defined so far).
  • ToString() shall not mutate the Report object
  • ToString() shall never throw an Exception

So the subclasses can easily override the ToString method, each one implementing a different behaviour, but all perfectly following the LSP.

On the other hand, if your contract would be

  • ToString() shall always return the empty string

then overriding it and return something different would violate the LSP - but such a contract would obviously make not much sense for any real world scenario.

Doc Brown
  • 218,378
2

First simple case: adding caching to an expensive operation. there is no functional difference between the original and the new function.

Also the original function could be documented to do something and as long as the overriding function still fits within that documentation then there is no problem (the core of the substitution principle). Remember you should code against the API not the implementation.

ratchet freak
  • 25,986
2

The Liskov Substitution Principle only says that subclasses should not violate provable properties of the supertype. The provable properties are basically the type signatures of members. So if a method on a superclass is declared to return an integer, then it shouldn't be overwritten in subclass to return a string.

Type systems in modern statically-typed OO languages generally prevent this from happening, so as an ordinary developer of say C# code you shouldn't be too worried about breaking the LSP (although I believe there are some weaknesses in the type system regarding Arrays which allows you to break LSP).

The principle is often understood more broadly (and vaguely) that subclasses should not "break expectations", which definitely a good design principle, but not really what the LSP says.

JacquesB
  • 61,955
  • 21
  • 135
  • 189
1

As an example, if you have a class with instances representing a user interface element, and that class has a "draw" method, then the "draw" method specification will not say exactly what will be drawn. It will instead say "the user interface element will be drawn appropriately to inform the user about all the state of the user interface element that is important to the user, in an intuitive and nice looking way". If you subclass that user interface element, the subclass draw method should and likely will act exactly according to the spec.

As a general principle, if the Liskov Substitution Principle made it impossible to subclass, that would be pretty a pretty stupid, and pretty stupid principles don't get names and Wikipedia entries and are quickly forgotten :-)

(BTW. It is an art that is too often forgotten to write a proper spec for a method that is supposed to be overridden in a subclass, because any existing behaviours do not describe the spec, only a special case of it).

gnasher729
  • 49,096
0

By definition different things? Whaddayoumean?

Liskov says you should not "go against" your ancestor's behavior. Doesn't mean that you cannot extend or augment it by doing stuff in addition to that base behavior.

In your overridden method you call inherited to execute the ancestor's behavior (honoring Liskov) and then can do stuff in addition to that.

Not calling inherited at all (*) would be a Liskov violation (unless you re-code the base's implementation, which I'd strongly advice against). Update @Ratchetfreak actually has a pretty neat example where a descendant would only call inherited if it hadn't already cached a result for an otherwise "expensive" calculation.

(*) please note that I am used to a language where you can call inherited regardless of whether the ancestor or the ancestor method is marked abstract. The compiler takes out the call.