3

在创建班级结构时,我正在努力遵守 Liskov 替换原则。我想在 Day 类中存储一组日历项目。需要有几种不同类型的 CalendarItems,例如:

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 单独收集一个集合吗?

4

1 回答 1

3

我认为您遇到的与其说是违反 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# 4dynamic中引入了关键字以允许运行时类型评估。因此,您可以做您想做的事,而无需自己编写双重调度,只需将您的项目转换为dynamic. 这迫使方法评估在运行时发生,因此将选择专门的覆盖:

var someData = new SomeData();
foreach(var item in calendarItems)
    someData.ProcessItem((dynamic)item);

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

于 2014-04-21T15:54:01.650 回答