94

Can you explain Liskov Substitution Principle (The 'L' of SOLID) with a good C# example covering all aspects of the principle in a simplified way? If it is really possible.

Smi
  • 12,505
  • 9
  • 53
  • 61
pencilCake
  • 45,443
  • 73
  • 211
  • 346
  • 9
    Here's a simplified way of thinking about it in a nutshell: If I follow LSP, I can replace any object in my code with a Mock object, and the nothing in the calling code would need to be adjusted or changed to account for the substitution. LSP is a fundamental support for the Test by Mock pattern. – kmote Jan 09 '14 at 17:16
  • There are some more examples of conformance and violations in [this answer](http://stackoverflow.com/q/20861107/314291) – StuartLC Oct 06 '16 at 06:29

3 Answers3

131

(This answer has been rewritten 2013-05-13, read the discussion in the bottom of the comments)

LSP is about following the contract of the base class.

You can for instance not throw new exceptions in the sub classes as the one using the base class would not expect that. Same goes for if the base class throws ArgumentNullException if an argument is missing and the sub class allows the argument to be null, also a LSP violation.

Here is an example of a class structure which violates LSP:

public interface IDuck
{
   void Swim();
   // contract says that IsSwimming should be true if Swim has been called.
   bool IsSwimming { get; }
}

public class OrganicDuck : IDuck
{
   public void Swim()
   {
      //do something to swim
   }

   bool IsSwimming { get { /* return if the duck is swimming */ } }
}

public class ElectricDuck : IDuck
{
   bool _isSwimming;

   public void Swim()
   {
      if (!IsTurnedOn)
        return;

      _isSwimming = true;
      //swim logic            
   }

   bool IsSwimming { get { return _isSwimming; } }
}

And the calling code

void MakeDuckSwim(IDuck duck)
{
    duck.Swim();
}

As you can see, there are two examples of ducks. One organic duck and one electric duck. The electric duck can only swim if it's turned on. This breaks the LSP principle since it must be turned on to be able to swim as the IsSwimming (which also is part of the contract) won't be set as in the base class.

You can of course solve it by doing something like this

void MakeDuckSwim(IDuck duck)
{
    if (duck is ElectricDuck)
        ((ElectricDuck)duck).TurnOn();
    duck.Swim();
}

But that would break Open/Closed principle and has to be implemented everywhere (and thefore still generates unstable code).

The proper solution would be to automatically turn on the duck in the Swim method and by doing so make the electric duck behave exactly as defined by the IDuck interface

Update

Someone added a comment and removed it. It had a valid point that I'd like to address:

The solution with turning on the duck inside the Swim method can have side effects when working with the actual implementation (ElectricDuck). But that can be solved by using a explicit interface implementation. imho it's more likely that you get problems by NOT turning it on in Swim since it's expected that it will swim when using the IDuck interface

Update 2

Rephrased some parts to make it more clear.

kame
  • 16,824
  • 28
  • 95
  • 142
jgauffin
  • 95,399
  • 41
  • 227
  • 352
  • 1
    @jgauffin: Example is simple and clear. But the solution you propose, first: breaks the Open-Closed Principle and it does not fit to Uncle Bob's definition (see the conclusion part of his article) which writes:"The Liskov Substitution Principle (A.K.A Design by Contract) is an important feature of all programs that conform to the Open-Closed principle." see:http://www.objectmentor.com/resources/articles/lsp.pdf – pencilCake Dec 13 '10 at 12:46
  • 1
    I don't see how the solution breaks Open/Closed. Read my answer again if you are referring to the `if duck is ElectricDuck` part. I had a seminar about SOLID last Thursday :) – jgauffin Dec 13 '10 at 12:48
  • Not really on topic, but could you please change your example so that you don't do the type-checking twice? A lot of developers aren't aware of the `as` keyword, which actually saves them from a lot of type-checking. I'm thinking something like the following: `if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();` – Siewers Aug 22 '11 at 10:56
  • 3
    @jgauffin - I am slightly confused by the example. I thought the Liskov Substitution Principle would still be valid in this case because Duck and ElectricDuck both derive from IDuck and you can put an ElectricDuck or Duck anywhere IDuck is used. If ElectricDuck has to turn on the before the duck can swim, isn't that the responsibility of the ElectricDuck or some code instantiating ElectricDuck and then setting the IsTurnedOn property to true. If this violates LSP, it seem's that LSV would be very hard to adhere to as all interfaces would contain different logic for it's methods. – Xaisoft Oct 24 '11 at 19:32
  • LSP says that all derived classes should work as the contract of the base class (or interface) specifies. In this case a Swim method should make the duck swim. It like you had a car interface with a `Break` method that the class `SportsCar` didn't do anything with because breaking is no fun in a Ferrari. Imagine the surprise of the user when he presses the break pedal. – jgauffin Oct 25 '11 at 04:48
  • @jgauffin - I disagree. there is no requirement of the LSP that says that the object actually has to perform the same action, or do anything useful at all. LSP talks only about the interface, or contract. LSP says that you can treat anything that is a duck as a duck, that doesn't mean it will function as a duck, just that it has the same interface. You could, for instance, make the duck quack every time you call Swim, and that would not break LSP since it's still contractually the same. – Erik Funkenbusch May 12 '13 at 19:36
  • It's true that LSP also talks about behavioral contracts, but this is in relation to the contact itself. The example being a Square derived from a Rectangle. Forcing the dimensions to be the same violates the contract that a Rectangle has independantly settable dimensions. Setting X=1 and Y=2 if X=2 will fail (thus the interface contract has been violated). Since a Duck behavior is not part of the contract, it doesn't fall under LSP. If you had a method that called Swim, and there was a property called "IsSwimming" which should be true (but isn't) if you call Swim, then that's a violation. – Erik Funkenbusch May 12 '13 at 19:49
  • 1
    @MystereMan: imho LSP is all about behavioral correctness. With the rectangle/square example you get the side effect of the other property being set. With the duck you get the side effect of it not swimming. LSP: `if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).` – jgauffin May 13 '13 at 06:27
  • Let's take a real world example instead. You have a `IUserRepository` with a save method. The save method works fine in the default repository (using an OR/M). But when you change it to use a WCF service instead it won't work for all user objects which aren't serializable. That makes the application not behaving as expected. – jgauffin May 13 '13 at 06:31
  • @jgauffin - Your example is correct, it would break LSP, but only because LSP specifies that you cannot throw any new exceptions that are not derived from any of the original exceptions. Trying to save a non-serializable object would throw a serialization error. If it did not throw that error, despite how the program functions, it would not violate LSP. "Correctness" means that when you call an interface, the interface does not do something unexpected. It does not mean that the actual behavior of the object can't be unexpected, only the interface. – Erik Funkenbusch May 13 '13 at 07:35
  • @jgauffin - it's a subtle distinction, but an important one. Otherwise LSP would make polymorphism essentially useless, since exchanging any object with another would violate LSP, since it's behavior is different from what was originally expected by the author of the code. Behavior refers to contractual behavior, not actual behavior of the object. – Erik Funkenbusch May 13 '13 at 07:39
  • I do not agree. What I mean with my WCF example was that the new repos would just ignore those objects. No error thrown. That WOULD brake the base contract. Same as if the IDuck contract specifies that Swim() should always swim. Not making it swim would break the base contract. Exceptions have nothing to do with LSP. – jgauffin May 13 '13 at 07:54
  • LSP is about making sure that all sub classes behave as specified by the base class. And that's the core problem with polymorphism. It's possible to inherit classes but not honoring `is-a` relationships. Doing so do always cause side effects since the base contract is not followed as expected – jgauffin May 13 '13 at 07:54
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/29822/discussion-between-jgauffin-and-mystere-man) – jgauffin May 13 '13 at 07:57
  • @jgauffin The only thing I don't understand is: Liskov Substitution Principle is all about using the right type of abstractions for your class. And in your example you have mentioned an organic duck, and an electric one. But wouldn't the example be better if you'd chose a green duck and a red duck? Because, if it looks like a duck, quacks like a duck, but needs batteries - You probaly have the wrong abstraction.. Nice example anyway! – Giellez Aug 10 '17 at 15:33
9

LSP a Practical Approach

Everywhere I look for LSP's C# examples, people have used imaginary classes and interfaces. Here is the practical implementation of LSP that I implemented in one of our systems.

Scenario: Suppose we have 3 databases (Mortgage Customers, Current Accounts Customers and Savings Account Customers) that provide customer data and we need customer details for given customer's last name. Now we may get more than 1 customer detail from those 3 databases against given last name.

Implementation:

BUSINESS MODEL LAYER:

public class Customer
{
    // customer detail properties...
}

DATA ACCESS LAYER:

public interface IDataAccess
{
    Customer GetDetails(string lastName);
}

Above interface is implemented by the abstract class

public abstract class BaseDataAccess : IDataAccess
{
    /// <summary> Enterprise library data block Database object. </summary>
    public Database Database;


    public Customer GetDetails(string lastName)
    {
        // use the database object to call the stored procedure to retrieve the customer details
    }
}

This abstract class has a common method "GetDetails" for all 3 databases which is extended by each of the database classes as shown below

MORTGAGE CUSTOMER DATA ACCESS:

public class MortgageCustomerDataAccess : BaseDataAccess
{
    public MortgageCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetMortgageCustomerDatabase();
    }
}

CURRENT ACCOUNT CUSTOMER DATA ACCESS:

public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
    public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetCurrentAccountCustomerDatabase();
    }
}

SAVINGS ACCOUNT CUSTOMER DATA ACCESS:

public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
    public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetSavingsAccountCustomerDatabase();
    }
}

Once these 3 data access classes are set, now we draw our attention to the client. In the Business layer we have CustomerServiceManager class that returns the customer details to its clients.

BUSINESS LAYER:

public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager
{
   public IEnumerable<Customer> GetCustomerDetails(string lastName)
   {
        IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>()
        {
            new MortgageCustomerDataAccess(new DatabaseFactory()), 
            new CurrentAccountCustomerDataAccess(new DatabaseFactory()),
            new SavingsAccountCustomerDataAccess(new DatabaseFactory())
        };

        IList<Customer> customers = new List<Customer>();

       foreach (IDataAccess nextDataAccess in dataAccess)
       {
            Customer customerDetail = nextDataAccess.GetDetails(lastName);
            customers.Add(customerDetail);
       }

        return customers;
   }
}

I haven't shown the dependency injection to keep it simple as its already getting complicated now.

Now if we have a new customer detail database we can just add a new class that extends BaseDataAccess and provides its database object.

Of course we need identical stored procedures in all participating databases.

Lastly, the client for CustomerServiceManagerclass will only call GetCustomerDetails method, pass the lastName and should not care about how and where the data is coming from.

Hope this will give you a practical approach to understand LSP.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
Yawar Murtaza
  • 3,110
  • 4
  • 28
  • 36
  • 3
    How can this is example of LSP? – somegeek Sep 07 '17 at 10:01
  • 1
    I don't see the LSP example in that either... Why does it have so many upvotes? – StaNov Nov 09 '17 at 06:54
  • 1
    @RoshanGhangare IDataAccess has 3 concrete implementations which can be substituted in the Business Layer. – Yawar Murtaza Nov 09 '17 at 08:29
  • 1
    @YawarMurtaza whatever you example you have quoted is typical implementation of strategy pattern that's it. Can you please clear where it is breaking LSP and how you solve that violation of LSP – Yogesh Dec 14 '17 at 11:45
  • @Yogesh - You can swap the implementation of IDataAccess with any of its concrete class and that wont effect the client code - thats what LSP is in nutshell. Yes, there are overlaps in certain design patterns. Secondly, above answer is only to show how LSP is implemented in a production system for a banking application. My intentions were not to show how LSP can be broken and how to fix it - that would be a training tutorial and you can find 100s of them on the web. – Yawar Murtaza Oct 01 '20 at 11:40
0

Here's the code for applying Liskov Substitute Principle.

public abstract class Fruit
{
    public abstract string GetColor();
}

public class Orange : Fruit
{
    public override string GetColor()
    {
        return "Orange Color";
    }
}

public class Apple : Fruit
{
    public override string GetColor()
    {
        return "Red color";
    }
}

class Program
{
    static void Main(string[] args)
    {
        Fruit fruit = new Orange();

        Console.WriteLine(fruit.GetColor());

        fruit = new Apple();

        Console.WriteLine(fruit.GetColor());
    }
}

LSV states: "Derived classes should be substitutable for their base classes (or interfaces)" & "Methods that use references to base classes (or interfaces) have to be able to use methods of the derived classes without knowing about it or knowing the details."

mark333...333...333
  • 1,136
  • 1
  • 9
  • 25