Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you explain Liskov Substitution Principle with a good C# example? [closed]

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.

like image 547
pencilCake Avatar asked Dec 13 '10 12:12

pencilCake


People also ask

Can you explain Liskov Substitution Principle?

Simply put, the Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. In other words, what we want is to have the objects of our subclasses behaving the same way as the objects of our superclass.

What is Liskov Substitution Principle C#?

The Liskov Substitution Principle (LSP) states that child class objects should be able to replace parent class objects without compromising application integrity.

What is the most accurate example of the Liskov Substitution Principle?

The classic example of the inheritance technique causing problems is the circle-elipse problem (a.k.a the rectangle-square problem) which is a is a violation of the Liskov substitution principle. A good example here is that of a bird and a penguin; I will call this dove-penguin problem.

How does Liskov Substitution Principle contribute to better design?

The Liskov Substitution Principle is the third of Robert C. Martin's SOLID design principles. It extends the Open/Closed principle and enables you to replace objects of a parent class with objects of a subclass without breaking the application. This requires all subclasses to behave in the same way as the parent class.


2 Answers

(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.

like image 110
jgauffin Avatar answered Oct 05 '22 21:10

jgauffin


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.

like image 21
Yawar Murtaza Avatar answered Oct 05 '22 23:10

Yawar Murtaza