Можете ли вы объяснить принцип замены Лискова хорошим примером C #?
Можете ли вы объяснить принцип замены Лискова («L» SOLID) с хорошим примером C #, охватывающим все аспекты принципа в упрощенном виде? Если это действительно возможно.
3 Solutions collect form web for “Можете ли вы объяснить принцип замены Лискова хорошим примером 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ов, не зная об этом или не зная подробностей «.