Можете ли вы объяснить принцип подстановки Лискова хорошим примером C #? - PullRequest
90 голосов
/ 13 декабря 2010

Можете ли вы объяснить принцип подстановки Лискова ('L' в SOLID) хорошим примером C #, упрощенно охватывающим все аспекты принципа?Если это действительно возможно.

Ответы [ 3 ]

125 голосов
/ 13 декабря 2010

(Этот ответ был переписан 2013-05-13, прочитайте обсуждение в нижней части комментариев)

LSP касается выполнения контракта базового класса.

Вы можете, например, не создавать новые исключения в подклассах, поскольку тот, который использует базовый класс, этого не ожидает.То же самое можно сказать, если базовый класс выбрасывает ArgumentNullException, если аргумент отсутствует, а подкласс допускает, что аргумент будет нулевым, также нарушение LSP.

Вот пример структуры класса, которая нарушает 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; } }
}

И вызывающий код

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

Как видите, есть два примера уток.Одна органическая утка и одна электрическая утка.Электрическая утка может плавать, только если она включена.Это нарушает принцип LSP, поскольку он должен быть включен, чтобы иметь возможность плавать, поскольку IsSwimming (который также является частью контракта) не будет установлен, как в базовом классе.

Вы можетеКонечно, решите это, выполнив что-то вроде этого

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

Но это нарушит принцип Open / Closed и должно быть реализовано повсеместно (и все же генерирует нестабильный код).

Правильным решением было бы автоматическое включение утки в методе Swim, и таким образом заставить электрическую утку вести себя точно так, как определено в интерфейсе IDuck

Обновление

Кто-то добавил комментарий и удалил его.У него была правильная точка, к которой я хотел бы обратиться:

Решение с включением утки в методе Swim может иметь побочные эффекты при работе с реальной реализацией (ElectricDuck).Но это можно решить с помощью явной реализации интерфейса .imho, скорее всего, у вас возникнут проблемы, НЕ включив его в Swim, так как ожидается, что он будет плавать при использовании IDuck интерфейса

Update 2

Перефразировал некоторые части, чтобы сделать их более понятными.

8 голосов
/ 10 мая 2016

LSP - практический подход

Везде, где я ищу примеры LSP на C #, люди использовали воображаемые классы и интерфейсы.Вот практическая реализация LSP, которую я реализовал в одной из наших систем.

Сценарий. Предположим, у нас есть 3 базы данных (клиенты по ипотечным кредитам, клиенты по текущим счетам и клиенты по сберегательному счету), которые предоставляют данные клиентов, и нам нужны данные клиентовдля данной фамилии клиента.Теперь мы можем получить более 1 информации о клиентах из этих 3 баз данных по данной фамилии.

Реализация:

БИЗНЕС МОДЕЛЬНЫЙ СЛОЙ:

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

Слой доступа к данным:

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

Выше интерфейс реализован абстрактным классом

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
    }
}

Этот абстрактный класс имеет общий метод "GetDetails" для всех 3базы данных, которые расширяются каждым классом базы данных, как показано ниже

ДОСТУП К ДАННЫМ ЗАКАЗЧИКА ЗАКАЗЧИКА:

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

ТЕКУЩИЙ ДОСТУП К СЧЕТУ ЗАКАЗЧИКА:

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

СБЕРЕЖЕНИЕКЛИЕНТСКИЙ ДОСТУП К ДАННЫМ:

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

Как только эти 3 класса доступа к данным установлены, теперь мы обращаем наше внимание на клиента.На бизнес-уровне у нас есть класс CustomerServiceManager, который возвращает сведения о клиенте своим клиентам.

БИЗНЕС-СЛОЙ:

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;
   }
}

Я не показал внедрение зависимости вбудь проще, потому что сейчас уже сложно.

Теперь, если у нас есть новая база данных о клиентах, мы можем просто добавить новый класс, который расширяет BaseDataAccess и предоставляет его объект базы данных.

Конечно, нам нужны идентичные хранимые процедуры во всех участвующих базах данных.

Наконец, клиент для CustomerServiceManager класса будет вызывать только метод GetCustomerDetails, передавать lastName и не должен заботиться о том, как и откуда поступают данные.

Надеюсь, это даст вам практическийподход к пониманию LSP.

0 голосов
/ 11 апреля 2018

Вот код для применения принципа замены Лискова.

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 заявляет: «Производные классы должны заменять свои базовые классы (или интерфейсы)» & «Методы, использующие ссылки на базовые классы (или интерфейсы), должны иметь возможность использовать методы производных классов, не зная об этом или не зная деталей.»

...