15

The LSP says "The derived types must not change the behavior of the base types", in other words "Derived types must be completely replaceable for their base types."

This means that if we define virtual methods in our base classes, we have violated this principle.

Also if we hide a method in the drive method by using new keyword then again we have violated this principle.

In other words, if we use polymorphism we have violated LSP!

In many applications I've used Virtual methods in the base classes and now I realize it violates LSP. Also if you use Template Method pattern you have violated this principle that I've used it a lot.

So, how to design your application that complies with this principle when you'd need inheritance and you'd like to benefit also from polymorphism? I'm confused!

See the example from here: http://www.oodesign.com/liskov-s-substitution-principle.html

Charles
  • 50,943
  • 13
  • 104
  • 142
The Light
  • 26,341
  • 62
  • 176
  • 258
  • 6
    "*The LSP says "The derived types must not change the behavior of the base types"*" - that's not what it says. – Oliver Charlesworth Jan 18 '13 at 16:54
  • Have you looked into the [SOLID](http://en.wikipedia.org/wiki/Solid_(object-oriented_design) design pattern? – Brian Jan 18 '13 at 16:55
  • @Brian SOLID is not a design pattern, it's a set of principle and LSP is one of them! – The Light Jan 18 '13 at 16:55
  • 4
    LSP states _“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”_. What is your source? – Austin Salonen Jan 18 '13 at 16:57
  • @OliCharlesworth to clarify further; if you change the behavior of a base class for example by overriding or hiding one of its methods, you can't replace its derived class with the base class so it violates LSP. – The Light Jan 18 '13 at 16:58
  • 1
    LSP say that any subtype of an object should be able to be substituted for the base type without needing to alter the program. For example if I have abstract my data access, I should be able to swap out a database implemention with a file system implemention without needing to modify the program – JG in SD Jan 18 '13 at 16:58
  • by replacement it does not mean replacement of functionality, rather it's syntax. I.E. you can replace subclass.Func1(p1, p2) by base.Func1(p1,p2) and you'll get the behavior of base. – AD.Net Jan 18 '13 at 16:59
  • 1
    @TheLight: I'm afraid you are misinformed; LSP is not about changing behaviour, it's about changing correctness/interface/contract. – Oliver Charlesworth Jan 18 '13 at 16:59
  • @OliCharlesworth that's not correct based on the definitions and examples I've seen from LSP :) - see this example and definition: http://www.oodesign.com/liskov-s-substitution-principle.html – The Light Jan 18 '13 at 17:07
  • @TheLight: I suggest starting with the Wikipedia article for a more representative explanation ;) – Oliver Charlesworth Jan 18 '13 at 17:10
  • LSP is not about the contract/interface of derived/base classes; that's by default anyway! If a method accepts a Derived class of course it can accept its base class but that's not the point. – The Light Jan 18 '13 at 17:13
  • "If a method accepts a Derived class of course it can accept its base class" -- try this in C#. – Austin Salonen Jan 18 '13 at 17:15
  • @TheLight: "Contract" in the sense of invariants. In other words, the program that uses the object shouldn't need to know which type it's using, nor have to modify its own behaviour to compensate. – Oliver Charlesworth Jan 18 '13 at 17:16
  • I see what you mean now about "correctness" of the code, thanks. – The Light Jan 18 '13 at 18:10
  • "In other words, if we use polymorphism we have violated LSP!" Thats completely correct. And thats the reason to stay away from stupid "SOLID"s and other pseudoscientific contradicting theories. – luke1985 Jan 05 '14 at 17:42

6 Answers6

10

Barbara Liskov has a very good article Data Abstraction and Hierarchy where she specifically touches polymorphic behavior and virtual software constructions. After reading this article you can see, that she describes in deep how software component can achieve flexibility and modularity from simple polymorphic calls.

LSP states about implementation details, not abstractions. Specifically, if you consume some interface or abstraction of type T, you should expect to pass all subtypes of T and not to observe unexpected behavior or program crash.

The keyword here is unexpected, because it can describe any of the properties of your program (correctness, task performed, returned semantics, temporarily and so on). So making you methods virtual does not mean by itself violating LSP

Ilya Ivanov
  • 23,148
  • 4
  • 64
  • 90
  • this makes sense. so it is about behavior exactly not contracts. – The Light Jan 18 '13 at 17:54
  • Isn't correctness to have the expected result?. I mean... if you have a calculator, Correctness would be to get the result without error. Correctness would be defined by the use cases.... – Taochok Aug 02 '16 at 07:48
3

LSP says that you must be able to use a derived class in the same way you use it's superclass: "objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program". A classic inheritance that breaks that rule is deriving Square class from Rectangle class since the former must have Height = Width, while the latter can have Height != Width.

public class Rectangle
{
    public virtual Int32 Height { get; set; }
    public virtual Int32 Width { get; set; }
}

public class Square : Rectangle
{
    public override Int32 Height
    {
        get { return base.Height; }
        set { SetDimensions(value); }
    }

    public override Int32 Width
    {
        get { return base.Width; }
        set { SetDimensions(value); }
    }

    private void SetDimensions(Int32 value)
    {
        base.Height = value;
        base.Width = value;
    }
}

In this case, the behavior of Width and Height properties changed and this is a violation of that rule. Let's take the output to see WHY the behavior changed:

private static void Main()
{
    Rectangle rectangle = new Square();
    rectangle.Height = 2;
    rectangle.Width = 3;

    Console.WriteLine("{0} x {1}", rectangle.Width, rectangle.Height);
}

// Output: 3 x 2
Tommaso Belluzzo
  • 23,232
  • 8
  • 74
  • 98
  • 1
    It violates LSP not because the code is wrong or has any issue at runtime. It violates LSP because it's not a correct behaviour that it should have. – The Light Jan 18 '13 at 17:57
  • Sorry... I don't understand. What do you mean? – Tommaso Belluzzo Jan 18 '13 at 17:58
  • I mean who defines what is "correct" and what is not correct? The same codes might be correct and not violating LSP in one application but in another it violates LSP because the definition of correctness would be different in that application. – The Light Jan 18 '13 at 18:15
  • @TheLight: The designer of the program. – Oliver Charlesworth Jan 18 '13 at 18:17
  • 1
    No... there is an absolute definition of correctness which can be commonly defined. It's like saying "I has driving" is correct just because you decide that is correct for you. There are common grammar rules that have been formulated and widely accepted by all the "final users". The same is for programming languages. – Tommaso Belluzzo Jan 18 '13 at 18:20
  • 1
    I disagree that this code example violates LSP. Firstly rectangles do not necessarily have `Width != Height`. Secondly if you can change the individual dimensions of a Rectangle you should be able to change them together in a Square. All Square does is call public properties of Rectangle, hence it is not incompatible with Rectangle's behavior. If Rectangle had a method SetDimensions(w, h) then Square would break LSP. – jwg May 28 '13 at 11:33
  • A square by definition is a rectangle that has equal length sides. All squares in basic geometry are in fact rectangles. The test is wrong, by assuming a subclass has no side-effects, not the implementation. Subclasses having side-effects do not violate LSP, only failing the base implementation does. A valid test would be setting width, testing width, setting height, testing height. The example provided is a violation of other principles (such as a property setter shouldn't update another property, though in the case of square that principle isn't worth retaining) but not LSP. – Jon Davis Sep 12 '15 at 19:18
  • 1
    Outputs 3 x 3, not 2 x 2 – Andrii Viazovskyi Sep 11 '18 at 08:49
  • I think this is a bad example. Like @bside already mentioned, this outputs 3x3 and not 3x2: https://dotnetfiddle.net/eyyP63. This is expected because of late/dynamic binding. Thats is the point of overriding the parent implementation. – Egon Olieux May 09 '19 at 19:44
  • Liskov is about pre and post conditions which define the "correctness". This canonical example of resizable square vs rectangle is problematic. It violates liskov because you can write a test that will fail using the derived type. It also has side effects assigning one value effects the other. Now if you would remove the the setters and use a constructor that sets widht and height equal. You have an immutable type such that a square can be a rectangle. Defining a square having a height and width is also just one implementation. You could just have one property "EdgeLength". – Wouter Jul 31 '22 at 17:32
3

"The derived types must not change the behavior of the base types" means that it must be possible to use a derived type as if you were using the base type. For instance, if you are able to call x = baseObj.DoSomeThing(123) you also must be able to call x = derivedObj.DoSomeThing(123). The derived method should not throw an exception if the base method didn't. A code using the base class should be able to work well with the derived class as well. It should not "see" that it is using another type. This does not mean that the derived class has to do exactly the same thing; that would be pointless. In other words using a derived type should not break the code that was running smoothly using the base type.

As an example let's assume that you declared a logger enabling you to log a message to the console

logger.WriteLine("hello");

You could use constructor injection in a class needing to produce logs. Now instead of passing it the console logger you pass it a file logger derived from the console logger. If the file logger throws an exception saying "You must include a line number in the message string", this would break LSP. However, it is not a problem that the logging goes to a file instead of the console. I.e. if the logger shows the same behavior to the caller, everything is okay.


If you need to write a code like the following one, then LSP would be violated:

if (logger is FileLogger) {
    logger.Write("10 hello"); // FileLogger requires a line number

    // This throws an exception!
    logger.Write("hello");
} else {
    logger.Write("hello");
}

By the way: The new keyword does not influence polymorphism, instead it declares a completely new method that happens to have the same name as a method in the base type but is not related to it. In particular, it is not possible to call it through a base type. For polymorphism to work, you must use the override keyword and the method must be virtual (unless you are implementing an interface).

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • If FileLogger inherits from ConsoleLogger so in what situation/s the program would fail to accept the FileLogger (derived class)?! can you make an example with some properties or methods? – The Light Jan 18 '13 at 17:45
  • that's the example for OCP violation. – The Light Jan 18 '13 at 17:48
  • If the file logger requires the message to be formatted in a specific way (I mentioned a line number as an example) but not the console logger, this would viloate LSP, since code using the logger would most probably have to be changed in order to be able to use the new logger. – Olivier Jacot-Descombes Jan 18 '13 at 17:51
  • No, OCP handles the extensibility of a software entity, where as LSP handles the compatibility of a derived type. – Olivier Jacot-Descombes Jan 18 '13 at 17:57
  • No, you're explaining the OCP not LSP. LSP is about whether the behavior is still correct for all the subtypes of a base. If you're checking for a specific derived type then having a different implementation then you're violating OCP because later for DatabaseLogger you may want to have a different formatting in your example. – The Light Jan 18 '13 at 18:06
  • The code calling the logger method does not want to have a different behavior, it its required to call the method differently because the derived type has special requirements. Instead it should be able to call the logger exactly the same way: `logger.Write("hello"); // Should work with ConsoleLogger as well as with FileLogger` – Olivier Jacot-Descombes Jan 18 '13 at 18:16
  • If the derived class requires special treatments it would certainly be OCP violation not LSP. It could be LSP violation if you say calling logger.Write("hello") produces an unexpected result where it shouldn't but if it's just formatting and your example, the FileLogger can have its own implementation of Write and add the formatting there. So I mean it's not a good example for LSP violation, sorry! – The Light Jan 18 '13 at 18:29
  • You don't understand my example. It's not the FileLogger which is formatting in a special way. The FileLogger requires the **caller** to format the input in a special way, but the ConsoleLogger didn't. Therefore the code of the caller **must** be changed if the FileLogger is used instead of the ConsoleLogger. This violates LSP. The caller should not even notice that it is using another logger. My code example is an example where LSP is violated. – Olivier Jacot-Descombes Jan 18 '13 at 18:44
2

I think the Liskov's Substitution Principle (LSP) is mainly about moving the implementation of functions that may differ to the children classes and leave the parent class as general as possible.

So whatever you change in the child class, it does not break the Liskov's Substitution Principle (LSP) as long as this change does not force you to modify the code in the parent class.

M. A. Kishawy
  • 5,001
  • 11
  • 47
  • 72
0

Subtypes must be replaceable by base types.

In terms of contacts.

Derived class can replace base class pre-condition for the same or weaker and post-condition for the same or greater.

Link

Hamlet Hakobyan
  • 32,965
  • 6
  • 52
  • 68
-1

For polymorphism to work, LSP must be being adhered to. A fine way to break it would be to introduce methods in a derived type that aren't in the base type. In that instance, polymorphism can't work because those methods are not available in the base type. You can have a different subtype implementation of a method, whilst adhering to both polymorphism and LSP.

levelnis
  • 7,665
  • 6
  • 37
  • 61
  • 3
    "Introduc[ing] methods in a derived type" doesn't necessarily break the contract the parent type defines. – Austin Salonen Jan 18 '13 at 17:04
  • But would having a method only available in a derived type, thereby forcing a cast before that behaviour is accessible, not be a violation of that principle? The calling type would need to be aware of which derived type it was using when it shouldn't care. Obviously that's contrary to polymorphism, but does it break LSP? – levelnis Jan 18 '13 at 17:20
  • 1
    The point is that where a ParentType is required any ChildType should suffice without breaking anything. If the ChildType _required_ a function only it defines to be called, then yes, that would violate LSP. Additional functionality doesn't in itself cause violations of LSP. See the `Stream` & `FileStream` classes. `FileStream` has additional functionality but it can still be used for anything that only requires a `Stream`. – Austin Salonen Jan 18 '13 at 17:30