在创建我的类结构时,我正在努力遵守Liskov替换原则。我希望在Day类中存储一组日历项。需要有几种不同类型的CalendarItem,例如:
AppointmentItem
NoteItem
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都需要一个单独的集合吗?
答案 0 :(得分:2)
我认为你遇到的不是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);
这引入了您可能想要捕获和处理的潜在运行时异常 - 这就是为什么有些人不喜欢这样做的原因。相比之下它目前也很慢,因此不建议在对性能敏感的紧密循环中使用它。