创建班级结构时,我努力遵守Liskov替代原则。我想在Day类中存储一个日历项集合。需要有几种不同类型的CalendarItem,例如:
约会项目
注意事项
RotaItem
它们都共享一些公共功能,这些功能在抽象基类CalendarItem中呈现:
public abstract class CalendarBaseItem
{
public string Description { get; private set; }
public List<string> Notes { get; private set; }
public TimeSpan StartTime { get; private set; }
public TimeSpan EndTime { get; private set; }
public int ID { get; private set; }
public DateTime date { get; private set; }
code omitted...
}
但是例如RotaItem具有一些额外的功能:
public class RotaItem : CalendarBaseItem
{
public string RotaName { get; private set; }
private bool spansTwoDays;
public bool spanTwoDays()
{
return this.spansTwoDays;
}
}
其他类也添加了自己的逻辑等。
我的课上有CalendarBaseItem的集合:
List<CalendarBaseItem> calendarItems;
但是在回顾这一点时,我发现我违反了LSP原理,因为我必须检查并强制转换每种具体类型,才能获得每个子类所需的功能。
如果有人可以建议如何避免此问题,我将不胜感激。我是否应该使用合成方法并将CalendarItem类添加到每个最终类中,例如
public class RotaItem
{
private CalendarBaseItem baseItem;
public string RotaName { get; private set; }
private bool spansTwoDays;
public RotaItem(baseArgs,rotaArgs)
{
baseItem = new CalendarBaseItem(baseArgs);
}
public bool spanTwoDays()
{
return this.spansTwoDays;
}
}
唯一的问题是,我的Day类中的每个Concrete CalendarItem都需要一个单独的集合?
最佳答案
我认为您所遇到的并不仅仅是Liskov替代原则的违反,而是您在大多数语言中都遇到了多态性限制。
使用List<CalendarBaseItem>
之类的东西,编译器会推断您只在处理CalendarBaseItem
,如果CalendarBaseItem
是抽象的,显然就不可能成立,但这正是强类型语言的作用:只是被告知了CalendarBaseItem
,这就是它的使用范围。
有一些模式可以让您应对这种限制。最受欢迎的是双分派模式:多重分派的一种特殊化,它将方法调用分派给运行时类型。这可以通过提供一个覆盖来实现,该覆盖在分派时分派预期的方法。 (即“双重发送”)。由于缺乏细节,很难准确地与您的情况相关联。但是,如果您想基于某种其他类型进行某种处理,例如:
public abstract class CalendarBaseItem
{
abstract void Process(SomeData somedata);
//...
}
public class RotaItem : CalendarBaseItem
{
public override void Process(SomeData somedata)
{
// now we know we're dealing with a `RotaItem` instance,
// and the specialized ProcessItem can be called
someData.ProcessItem(this);
}
//...
}
public class SomeData
{
public void ProcessItem(RotaItem item)
{
//...
}
public void ProcessItem(NoteItem item)
{
//...
}
}
它将替换为:
var someData = new SomeData();
foreach(var item in calendarItems)
someData.ProcessItem(item);
现在,这就是在C#中完成的“经典”方式-跨所有C#版本。在C#4中,引入了
dynamic
关键字以允许运行时类型评估。因此,您只需将项目转换为dynamic
即可完成自己想要的事情,而不必自己编写两次发货。这迫使方法评估在运行时进行,因此将选择专门的替代:var someData = new SomeData();
foreach(var item in calendarItems)
someData.ProcessItem((dynamic)item);
这引入了您可能想捕获并处理的潜在运行时异常-这就是为什么某些人不太喜欢这种异常的原因。目前比较起来还很慢,因此不建议在对性能敏感的紧密循环中使用。
关于c# - 继承与李斯科夫替代原则,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23199725/