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?

1
  • 5
    Overriding isn't intrinsically a violation of LSP; it's the entire point of LSP. Commented Jun 29, 2015 at 15:23

5 Answers 5

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.

12
  • 7
    In a professional environment (I'm a hobbyist), would this 'contract' be defined anywhere? E.g. in the documentation? Or is it only in the programmer's head?
    – Aviv Cohn
    Commented Jun 12, 2014 at 13:15
  • 3
    @Prog: I would not see this as a property of "professional environments". As you might have noticed, "Design by contract" is explicitly mentioned at the top of the Wikipedia article about LSP (en.wikipedia.org/wiki/Liskov_substitution_principle). The support in different programming languages is different, some languages allow explictly the definition of post-/preconditions and invariants. The contract can always be defined in the documentation (including examples or unit tests, which are part of the docs).
    – Doc Brown
    Commented Jun 12, 2014 at 13:33
  • Non-private members (function signatures, properties, fields) are also part of the contract. Generally, statically typed languages enforce this during compilation.
    – Brian
    Commented Jun 12, 2014 at 19:00
  • @Brian: absolutely, sometimes I forget the obvious things.
    – Doc Brown
    Commented Jun 12, 2014 at 20:13
  • 1
    @Benjohn: I added a link to the word "contract" in my answer, should clarify. I already mentioned what I meant by this term in the second comment below my answer.
    – Doc Brown
    Commented Aug 21, 2016 at 11:43
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.

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.

4
  • I'm confused by the assertion that LSV is mostly type-checkable. How does that square with e.g. the history rule, or enforcement of contracts. It seems clear from this that LSV is about behavior: en.wikipedia.org/wiki/Behavioral_subtyping (and if even if it wasn't, prior work by Meyer directly deals with contracts & behavior). Do you have a source?
    – johncip
    Commented Feb 16, 2022 at 19:56
  • Can you please cite a source for your first sentence?
    – user949300
    Commented May 14 at 18:17
  • 1
    @user949300: "Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T." A behavioral notion of subtyping, Liskov, Barbara; Wing, Jeannette (1994-11-01).
    – JacquesB
    Commented May 15 at 5:59
  • @JacquesB Thanks. I still have no idea what they are talking about. :-)
    – user949300
    Commented May 15 at 17:26
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).

1
  • Unfortunately, many stupid principles get names and Wikipedia entries.
    – user949300
    Commented May 14 at 18:18
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.

6
  • 2
    More generally, LSP is predicated on subtype behaviour, not subclass behaviour. Take a List abstract datatype, for example. "List" has no behaviour inherently, it has a contract it must hold to. An array is a list, as is a proxy to a remote ListServer. They operate in entirely different ways, but both hold to the contract. There's no "base behaviour" there, they just need to be swappable.
    – Phoshi
    Commented Jun 12, 2014 at 11:09
  • suggesting that you should almost always call the base method when overriding strikes me as wrong as it implies your method now breaks the SRP
    – jk.
    Commented Jun 12, 2014 at 12:36
  • @jk. How? Augmented behavior in payment programs could be a getting a value from somewhere for the calculation of rebates, an additional rebate, ... whatever ... all variations on calculating (base) rebates from the ancestor, all still within SRP. Commented Jun 12, 2014 at 17:57
  • @Phoshi: Yes, so? That doesn't preclude cases where you do have base behavior and descendants can add to it as necessary. After all a class is a type. Calculations in payment and salary programs for example. The ancestor will do the basic calculations, descendants may add extra's, effect different values for parts of the calculation depending on whatever. Of course in any salary program the calculations may well be an amalgamation of Strategy patterns and each may in effect have its own inheritance hierarchy to deal with all variables and variations. Commented Jun 12, 2014 at 18:03
  • @MarjanVenema: Because you're focussing on specific examples instead of getting to the core reasoning. You give specific examples of places where to not break LSP you use inheritance, but that's all they are--specific examples, not at all the general case.
    – Phoshi
    Commented Jun 15, 2014 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.