在创建我的班级结构时,我努力坚持利斯科夫替代原则。我想在Day类中存储一组日历项。需要有几种不同类型的日历项,例如:
任命项目
备注项目
轮换项目
它们都共享一些抽象基类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;
}
}
这里唯一的问题是,我将需要一个单独的收集,为每个具体的日历项目在我的日课?
我认为,您遇到的与其说是违反了Liskov替换原则,不如说是在大多数语言中遇到了多态性限制。
用类似于List的东西
有一些模式可以让你处理这种限制。最流行的是双调度模式:一种多调度的专门化,它将方法调用分派到运行时类型。这可以通过提供重写来实现,即当分派时,分派预期的方法。(即“双重分派”)。由于缺乏细节,很难准确地与你的情况联系起来。但是,如果你想做一些基于其他类型的处理,例如:
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
,您就可以随心所欲地完成任务,而无需自己编写双重分派。它强制在运行时进行方法评估,因此将选择专门的覆盖:
var someData = new SomeData();
foreach(var item in calendarItems)
someData.ProcessItem((dynamic)item);
这引入了您可能希望捕获和处理的潜在运行时异常——这就是为什么有些人不喜欢这样。相比之下,它目前也非常慢,因此不建议在性能敏感的紧密循环中使用它。
存在无法写入或查找的流派生类这一事实是否违反了Liskov替换原则? 例如,无法查找NetworkStream,如果调用方法,它将抛出。 还是因为存在标志就可以了? 考虑到众所周知的继承自的例子...将标志和添加到是否可以解决问题? 这难道不是打开了通过添加旗帜来解决问题的大门吗?
我发现很难理解这个概念。我脑子里有几个问题。我试着在网上查询,但是没有太多的资源。 子类是否需要在其整个生命周期中保持其独特性? 我很确定LSP定义了超级类和子类之间的契约,如果我错了,请纠正我。 如果一个给定的函数使用某个对象,你能用它的一个子类替换这个对象而不破坏它的执行吗? 如果有一个类型为超类的变量,程序是否仍然有效。如果我将该超类或任何子类的实例放入该变量中。 如果这没有道理,我很抱歉。
Java不允许
我在派生类中重写了带有附加先决条件的虚拟函数。这是快照- 如果我理解正确的话,这里的代码通过附加一个先决条件打破了Liskov替换-IsImmediateProcess和其他日期检查。对吗?或者一个被重写的函数调用一个基函数,然后向它添加自己的行为,这样可以吗? 我不能将重写方法中由初始过程类型引入的条件移动到基本类型,因为它是特定于初始过程的。 在这种情况下,如果派生类重写行为并希望在不违反Li
LSP定义指出,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性。 子类型中的前提条件不能加强 例如,我有下面的类,这是违反(在子类型中不能加强前提条件)。我正试图把我的头绕在这上面,请有人提供一个好的例子来理解它。