Liskov Substitution Principle (SOLID의 'L')을 단순화 된 방식으로 원리의 모든 측면을 다루는 좋은 C # 예제로 설명 할 수 있습니까? 정말 가능하다면.
답변:
(이 답변은 2013-05-13 다시 작성되었습니다. 댓글 하단의 토론을 읽으십시오.)
LSP는 기본 클래스의 계약을 따르는 것입니다.
예를 들어 기본 클래스를 사용하는 사람은이를 기대하지 않기 때문에 하위 클래스에서 새 예외를 throw 할 수 없습니다. ArgumentNullException
인수가 누락되고 하위 클래스가 인수가 null이되도록 허용하는 경우 기본 클래스가 throw하는 경우에도 마찬가지입니다. 또한 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();
}
보시다시피 오리의 두 가지 예가 있습니다. 유기농 오리 한 마리와 전기 오리 한 마리. 전기 오리는 켜져있을 때만 수영 할 수 있습니다. 이것은 IsSwimming
(계약의 일부이기도 한) 기본 클래스에서와 같이 설정되지 않기 때문에 수영 할 수 있도록 설정해야하기 때문에 LSP 원칙을 위반합니다 .
물론 이런 식으로 해결할 수 있습니다.
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
그러나 그것은 Open / Closed 원칙을 어 기고 모든 곳에서 구현되어야합니다 (그러므로 여전히 불안정한 코드를 생성합니다).
적절한 해결책은 Swim
메서드 에서 자동으로 오리를 켜고 그렇게함으로써 전기 오리가 IDuck
인터페이스에 정의 된대로 정확하게 작동하도록하는 것입니다.
최신 정보
누군가 댓글을 추가하고 삭제했습니다. 내가 다루고 싶은 유효한 점이 있습니다.
Swim
메서드 내에서 duck을 켜는 솔루션 은 실제 구현 ( ElectricDuck
)으로 작업 할 때 부작용이있을 수 있습니다 . 그러나 명시 적 인터페이스 구현 을 사용하여 해결할 수 있습니다 . imho 인터페이스를 Swim
사용할 때 헤엄 칠 것으로 예상되기 때문에 전원을 켜지 않으면 문제가 발생할 가능성이 더 큽니다.IDuck
업데이트 2
더 명확하게하기 위해 일부 부분을 수정했습니다.
if duck is ElectricDuck
부품을 언급하는 경우 내 대답을 다시 읽으십시오 . 지난 목요일 SOLID에 대한 세미나를 가졌습니다. :)
as
키워드를 인식하지 못하기 때문에 실제로는 많은 유형 검사에서 제외됩니다. 나는 다음과 같은 생각을하고 있습니다.if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
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).
LSP 실용적인 접근
LSP의 C # 예제를 찾는 모든 곳에서 사람들은 가상의 클래스와 인터페이스를 사용했습니다. 다음은 시스템 중 하나에서 구현 한 LSP의 실제 구현입니다.
시나리오 : 고객 데이터를 제공하는 3 개의 데이터베이스 (모기지 고객, 경상 계좌 고객 및 저축 계좌 고객)가 있고 주어진 고객의성에 대한 고객 세부 정보가 필요하다고 가정합니다. 이제 우리는 주어진 성을 기준으로 3 개의 데이터베이스에서 1 개 이상의 고객 세부 정보를 얻을 수 있습니다.
이행:
비즈니스 모델 계층 :
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
}
}
이 추상 클래스는 아래에 표시된 것처럼 각 데이터베이스 클래스에 의해 확장되는 3 개의 모든 데이터베이스에 대한 공통 메소드 "GetDetails"를 가지고 있습니다.
모기지 고객 데이터 액세스 :
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();
}
}
이 세 가지 데이터 액세스 클래스가 설정되면 이제 클라이언트에주의를 기울입니다. 비즈니스 계층에는 고객 세부 정보를 클라이언트에 반환하는 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를 이해하는 실용적인 접근 방식을 제공하기를 바랍니다.
다음은 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는 "파생 클래스는 기본 클래스 (또는 인터페이스)를 대체 할 수 있어야합니다."& "기본 클래스 (또는 인터페이스)에 대한 참조를 사용하는 메서드는 알지 못하거나 세부 정보를 알지 못해도 파생 클래스의 메서드를 사용할 수 있어야합니다. . "