我一直在阅读关于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。向子类添加null防护是违反LSP的,对吗?那么,我如何设计我的类,使它们不违反LSP,但我的一个子类需要一个辅助帐户持有者,而另一个不需要?
将这个问题与一个事实相结合,即可能有大量不同类型的帐户,这些帐户需要通过工厂或返回构建器或其他东西的工厂生成。
对于接口,我也有同样的问题。如果我有一个接口:
public interface IPrintsSomething
{
void PrintSomething(string text);
}在任何实现IPrintsSomething的类上为文本添加空保护子句不是违反了LSP吗?你如何保护你的不变量?这是正确的词,对吗?
发布于 2016-12-24 11:02:12
你应该研究tell-don ask,以及命令/查询分离,你可以从这里开始: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,那就是另一回事了。
但在这种情况下,tell-don-ask避免了整个问题。
发布于 2016-12-22 05:07:52
那么,我如何设计我的类,使它们不违反
,但是我的一个子类需要一个辅助帐户持有者,而另一个不需要?
解决这个问题的方法是通过将这种可变性呈现给基类的契约。它可能看起来像这样(省略了不必要的实现细节):
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的类上为文本添加空保护子句不是违反了LSP吗?
使验证成为接口契约中显而易见的一部分。多么?文档中,实现者必须检查null的text的值。
https://stackoverflow.com/questions/41271696
复制相似问题