我一直在读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
答案 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>