创建班级结构时,我努力遵守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/

10-09 02:52