Можете ли вы объяснить принцип замены Лискова хорошим примером C #?

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

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

    LSP идет о заключении контракта базового classа.

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

    Вот пример структуры classа, которая нарушает 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 (который также является частью контракта) не будет установлен как в базовом classе.

    Вы можете, конечно, решить это, сделав что-то вроде этого

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

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

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

    Обновить

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

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

    Обновление 2

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

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

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

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

    Реализация:

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

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

    ДАННЫЙ ДОСТУПНЫЙ СЛОЙ:

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

    Над интерфейсом реализован абстрактный class

     public abstract class BaseDataAccess : IDataAccess { ///  Enterprise library data block Database object.  public Database Database; public Customer GetDetails(string lastName) { // use the database object to call the stored procedure to retrieve the customer details } } 

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

    ДОСТУП ДАННЫХ КЛИЕНТОВ ИПОТЕКИ:

     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 classа доступа к данным установлены, теперь мы обращаем наше внимание на клиента. На бизнес-уровне у нас есть class CustomerServiceManager, который возвращает детали клиента своим клиентам.

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

     public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager { public IEnumerable GetCustomerDetails(string lastName) { IEnumerable dataAccess = new List() { new MortgageCustomerDataAccess(new DatabaseFactory()), new CurrentAccountCustomerDataAccess(new DatabaseFactory()), new SavingsAccountCustomerDataAccess(new DatabaseFactory()) }; IList customers = new List(); foreach (IDataAccess nextDataAccess in dataAccess) { Customer customerDetail = nextDataAccess.GetDetails(lastName); customers.Add(customerDetail); } return customers; } } 

    Я не показывал инъекцию зависимостей, чтобы сохранить ее просто, поскольку она уже усложняется.

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

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

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

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

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

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

    Давайте будем гением компьютера.