2016-12-21 4 views
0

저는 Liskov Substitution Principle (LSP)에 대해 읽었으며 올바르게 고수하는 방법에 대해서는 약간 혼란스러워합니다. 특히 인터페이스와 하위 클래스가 사용되는 경우. 나는 기본 클래스가있는 경우C# LSP 생성자 매개 변수 및 Guard 절

예를 들어, :

public abstract class AccountBase 
{ 
    private string primaryAccountHolder; 

    public string PrimaryAccountHolder 
    { 
     get { return this.primaryAccountHolder; } 
     set 
     { 
      if (value == null) throw ArgumentNullException("value"); 
      this.primaryAccountHolder = value; 
     } 
    } 

    public string SecondaryAccountHolder { get; set; } 

    protected AccountBase(string primary) 
    { 
     if (primary == null) throw new ArgumentNullException("primary"); 
     this.primaryAccountHolder = primary; 
    } 
} 

는 이제 가정 해 봅시다을 나는 기본 클래스에서 상속 두 개의 계정을 가지고있다. SecondaryAccountHolder를 필요로하는 사람. 하위 클래스에 null 가드를 추가하는 것은 LSP를 위반 한 것입니까? 맞습니까? 그렇다면 LSP를 위반하지 않고 하위 클래스 중 하나에 보조 계정 소유자가 필요하고 그렇지 않은 클래스를 디자인하는 방법은 무엇입니까?

화합물은 여러 가지 유형의 계정이있을 수 있다는 사실과 함께 작성자 또는 무언가를 반환하는 공장 또는 공장을 통해 생성되어야한다는 사실에 대한 질문입니다.

그리고 인터페이스에 대해 동일한 질문이 있습니다.

public interface IPrintsSomething 
{ 
    void PrintSomething(string text); 
} 

IPrintsSomething를 구현하는 모든 클래스에 텍스트 널 가드 조항을 추가하는 LSP 위반하지 않을까요 : 나는 인터페이스가 있다면? 불변량을 어떻게 보호합니까? 올바른 단어 맞습니까? : p

답변

0

그렇다면 LSP에 위배되지 않지만 하위 클래스 중 하나에 보조 계좌 소유자가 필요하고 그렇지 않은 클래스를 어떻게 디자인 할 수 있습니까?

이 문제의 근본적인 원인은이 변동성을 기본 클래스의 계약에 반영하는 것입니다. 그것은이 (왼쪽으로 불필요한 구현 세부 사항)처럼 보일 수 있습니다 : 다음

public abstract class AccountBase 
{ 
    public string PrimaryAccountHolder 
    { 
     get { … } 
     set { … } 
    } 

    public string SecondaryAccountHolder 
    { 
     get { … } 
     set 
     { 
      … 
      if (RequiresSecondaryAccountHolder && value == null) throw …; 
      … 
     } 
    } 

    public abstract bool RequiresSecondaryAccountHolder { get; } 
} 

그들이에 있거나 SecondaryAcccountHolder의 값을 제공했는지 여부 AccountBase의 사용자가 결정할 수 있기 때문에 당신의 LSP를 위반되지 않습니다.

그리고 인터페이스에 대해 동일한 질문이 있습니다. ... IPrintsSomething을 구현하는 클래스의 텍스트에 null 보호 절을 추가하는 것이 LSP 위반이 아닐까요?

유효성 검사를 인터페이스 계약의 분명한 부분으로 만듭니다. 방법? 문서, 구현자는 null에 대해 text 값을 확인해야합니다.

+0

AccountBase 예제의 경우 생성자와 어떻게 비슷하게 작동합니까? 그것은 기본 클래스의 생성자가 아닌 경우 SecondaryAccountHolder를 필요로하는 하위 클래스의 생성자를 LSP에 위배합니까? –

+0

생성자가 보호되어 있으므로 클래스의 공개 계약과 관련되지 않습니다. 서브 클래스는 관련된 프로퍼티를 diretly 사용하기 때문에 중복되는 것처럼 보입니다. 생성자를 실제로 원한다면 기본 및 보조 계정을 모두 수용 할 수 있지만 DRY로 유지하면 유효성 검사 논리가 중복되지 않고 대신 속성에 할당됩니다. –

+0

하지만 속성에 직접 할당하면 개체가 유효한 상태임을 보증하지 않습니다. 예를 들어, 기본 클래스에 "RequiresSecondaryAccountHolder"속성을 넣으면 속성을 null로 설정할 수 없으므로 속성이 실제로 설정되었는지 확인할 수 없습니다. 그렇다면 객체를 유효한 상태로 유지하기 위해 파생 클래스에서 생성자를 사용해야 할 필요가 없습니까? 파생 클래스에 대해 다른 생성자를 사용하는 것이 LSP를 위반합니까? –

1

당신은 연구해야-에게 안되 질문 및 명령/쿼리 분리, 당신은 여기에서 시작할 수 있습니다 : https://pragprog.com/articles/tell-dont-ask

당신은 당신이 그 (것)들을 원하는 어떤 객체에게 노력해야한다; 그들의 상태에 대해 질문하고, 결정을 내리고, 무엇을해야하는지 그들에게 말하지 마라.

속성과 관련하여 항상 원하는 것이 있습니다. 객체에 대해 물어 보지 마십시오.

대신이 같은 결정을 요구하고 만드는 :

string holders = account.PrimaryAccountHolder; 
if (accountHolder.SecondaryAccountHolder != null) 
{ 
    holders += " " + accountHolder.SecondaryAccountHolder; 
} 

그것을 알려주기 :

string holders = account.ListAllHoldersAsAString(); 

를 이상적으로, 당신은 실제로 당신이 실제로 그 문자열로 수행 할 작업을 말할 것 :

account.MailMergeAllAccountHoldersNames(letterDocument); 

이제 두 명의 계정 보유자를 처리하는 논리가 하위 클래스에 있습니다. 하나, 둘 또는 n 계정 소유자 일 수 있습니다. 호출 코드는 걱정하지 않거나 알 필요가 없습니다.

LSP의 경우 클라이언트가 처음부터 두 번째 소지자에서 null을 확인해야한다고 공식적으로 (또는 비공식적으로) 문서화 된 계약이있는 경우에도 문제가 없습니다. 좋은 것은 아니지만 null 포인터 예외는 클래스를 올바르게 사용하지 않는 클라이언트의 잘못입니다. 부울 속성을 추가하면이 기능이 향상된다는 것은 사실이 아니며 약간 더 읽기 쉽습니다. 즉, 누구든지 쓰기 전에 IList.IsReadOnly을 확인합니까?). 그런 다음 두 홀더 계정으로 시작하는 경우

그러나 가 계약을 변경 번째 계정 소유자는 다음, 나중에 하나의 계정에 대한 null를 할 수 있다는 그 조건, 기존 깰 수있는 단 하나의 인스턴스를 추가 암호. 계정을 사용하는 모든 장소를 완벽하게 제어 할 수 있다면 공개 앱인 의 변경 사항은 다른 문제입니다.

하지만이 경우 전체 문제를 피하십시오.