좋은 C # 예제로 Liskov Substitution Principle을 설명 할 수 있습니까? [닫은]


92

Liskov Substitution Principle (SOLID의 'L')을 단순화 된 방식으로 원리의 모든 측면을 다루는 좋은 C # 예제로 설명 할 수 있습니까? 정말 가능하다면.


9
간단히 말해 LSP를 따르는 경우 코드의 모든 개체를 Mock 개체로 바꿀 수 있으며 대체를 설명하기 위해 호출 코드의 아무것도 조정하거나 변경할 필요가 없습니다. LSP는 Test by Mock 패턴에 대한 기본 지원입니다.
kmote 2014 년

에서 좀 더 적합성의 예와 위반이 있습니다 이 답변
StuartLC

답변:


128

(이 답변은 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

더 명확하게하기 위해 일부 부분을 수정했습니다.


1
@jgauffin : 예는 간단하고 명확합니다. 그러나 먼저 제안하는 해결책은 개방-폐쇄 원칙을 깨고 Bob 삼촌의 정의 (그의 기사의 결론 부분 참조)에 맞지 않습니다. 개방-폐쇄 원칙을 준수하는 모든 프로그램의. " 참조 : objectmentor.com/resources/articles/lsp.pdf
pencilCake 2010

1
솔루션이 Open / Closed를 어떻게 중단하는지 모르겠습니다. if duck is ElectricDuck부품을 언급하는 경우 내 대답을 다시 읽으십시오 . 지난 목요일 SOLID에 대한 세미나를 가졌습니다. :)
jgauffin 2010

주제에 관한 것은 아니지만 유형 검사를 두 번하지 않도록 예제를 변경해 주시겠습니까? 많은 개발자가 as키워드를 인식하지 못하기 때문에 실제로는 많은 유형 검사에서 제외됩니다. 나는 다음과 같은 생각을하고 있습니다.if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
Siewers 2010-08-22

3
@jgauffin-예제에서 약간 혼란 스럽습니다. Duck과 ElectricDuck이 모두 IDuck에서 파생되고 IDuck이 사용되는 곳에 ElectricDuck 또는 Duck을 둘 수 있기 때문에 Liskov Substitution Principle이이 경우에도 여전히 유효하다고 생각했습니다. 오리가 수영하기 전에 ElectricDuck을 켜야하는 경우 ElectricDuck 또는 ElectricDuck을 인스턴스화 한 일부 코드의 책임이 아닌 다음 IsTurnedOn 속성을 true로 설정합니다. 이것이 LSP를 위반하면 모든 인터페이스가 메서드에 대해 다른 논리를 포함하므로 LSV를 준수하기가 매우 어려울 것입니다.
Xaisoft

1
@MystereMan : imho LSP는 행동의 정확성에 관한 것입니다. 사각형 / 정사각형 예제를 사용하면 설정되는 다른 속성의 부작용을 얻을 수 있습니다. 오리를 사용하면 수영하지 않는 부작용이 발생합니다. LSP :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).
jgauffin 2013 년

8

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를 이해하는 실용적인 접근 방식을 제공하기를 바랍니다.


3
이것이 어떻게 LSP의 예일 수 있습니까?
somegeek

1
저도 LSP 예제를 볼 수 없습니다 ... 왜 그렇게 많은 찬성표가 있습니까?
StaNov

1
@RoshanGhangare IDataAccess에는 비즈니스 계층에서 대체 할 수있는 세 가지 구체적인 구현이 있습니다.
Yawar Murtaza 2017

1
@YawarMurtaza 당신이 인용 한 예가 무엇이든 전략 패턴의 전형적인 구현입니다. 이 LSP를 파괴하고 당신이 LSP의 위반 해결 방법 어디 분명 기쁘게 할 수
인 Yogesh

0

다음은 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는 "파생 클래스는 기본 클래스 (또는 인터페이스)를 대체 할 수 있어야합니다."& "기본 클래스 (또는 인터페이스)에 대한 참조를 사용하는 메서드는 알지 못하거나 세부 정보를 알지 못해도 파생 클래스의 메서드를 사용할 수 있어야합니다. . "

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.