当前位置: 首页 > 知识库问答 >
问题:

继承与Liskov替换原理

陆才俊
2023-03-14

在创建我的班级结构时,我努力坚持利斯科夫替代原则。我想在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;
    }

}

这里唯一的问题是,我将需要一个单独的收集,为每个具体的日历项目在我的日课?

共有1个答案

麻学博
2023-03-14

我认为,您遇到的与其说是违反了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定义了超级类和子类之间的契约,如果我错了,请纠正我。 如果一个给定的函数使用某个对象,你能用它的一个子类替换这个对象而不破坏它的执行吗? 如果有一个类型为超类的变量,程序是否仍然有效。如果我将该超类或任何子类的实例放入该变量中。 如果这没有道理,我很抱歉。

  • 我在派生类中重写了带有附加先决条件的虚拟函数。这是快照- 如果我理解正确的话,这里的代码通过附加一个先决条件打破了Liskov替换-IsImmediateProcess和其他日期检查。对吗?或者一个被重写的函数调用一个基函数,然后向它添加自己的行为,这样可以吗? 我不能将重写方法中由初始过程类型引入的条件移动到基本类型,因为它是特定于初始过程的。 在这种情况下,如果派生类重写行为并希望在不违反Li

  • LSP定义指出,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性。 子类型中的前提条件不能加强 例如,我有下面的类,这是违反(在子类型中不能加强前提条件)。我正试图把我的头绕在这上面,请有人提供一个好的例子来理解它。