继承和Liskov替换原则

时间:2014-04-21 14:47:13

标签: c# design-patterns liskov-substitution-principle

在创建我的类结构时,我正在努力遵守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都需要一个单独的集合吗?

1 个答案:

答案 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);

这引入了您可能想要捕获和处理的潜在运行时异常 - 这就是为什么有些人不喜欢这样做的原因。相比之下它目前也很慢,因此不建议在对性能敏感的紧密循环中使用它。