C#LSP构造函数参数和Guard子句

时间:2016-12-21 20:51:44

标签: c# design-patterns solid-principles liskov-substitution-principle

我一直在读Liskov替换原则(LSP),我对你如何正确地遵守它有点困惑。特别是在使用接口和子类时。

例如,如果我有一个基类:

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的一个。向子类添加空保护是违反LSP的,对吗?那么我如何设计我的课程以使他们不违反LSP,但我的一个子课程需要一个二级帐户持有人而另一个不需要?

将问题与可能存在大量不同类型的帐户的事实相结合,并且它们需要通过返回建筑商或其他东西的工厂或工厂生成。

我对接口有同样的问题。如果我有一个界面:

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

在任何实现IPrintsSomething的类上为文本添加null保护子句会不会违反LSP?你如何保护你的不变量?那是正确的话吗? :P

2 个答案:

答案 0 :(得分: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,那么这很好。它并不好,但任何空指针异常将是客户端未正确使用该类的错误。 (注意,添加一个布尔属性对此有所改进,它可能更具可读性,即有人在写入之前检查IList.IsReadOnly吗?!)。

但是,如果您开始使用双持有人帐户,然后添加了第二个帐户持有人稍后null个单一帐户的条件,那么您已更改合同,并且单个实例可能会破坏现有代码。如果您完全控制所有使用帐户的地方,那么您可以这样做,如果这是公共API 你改变了,这是另一回事。

但是告诉 - 不要在这种情况下避免整个问题。

答案 1 :(得分: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; }
}

然后您没有违反LSP,因为AccountBase的用户可以确定他们是否必须提供SecondaryAcccountHolder的值。

  

我对接口有同样的问题。 ...在实现IPrintsSomething的任何类中为文本添加null保护子句不会违反LSP吗?

使验证成为界面合同的明显部分。怎么样?记录,实现者必须为text null的价值config/gitlab.yml。{/ p>

相关问题