简体   繁体   English

继承和Liskov替换原则

[英]Inheritance and Liskov substitution principle

I am struggling to adhere to Liskov substitution principle when creating my class structure. 在创建我的类结构时,我正在努力遵守Liskov替换原则。 I want to have a Collection of calendar items stored within a Day class. 我希望在Day类中存储一组日历项。 There need to be several different type of CalendarItems eg: 需要有几种不同类型的CalendarItem,例如:

AppointmentItem AppointmentItem
NoteItem NoteItem
RotaItem RotaItem

they all share some common functionality which is presnt in the abstract base class CalendarItem: 它们都共享一些在抽象基类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...
}

but then for example RotaItem has some extra functionality: 但是后来例如RotaItem有一些额外的功能:

public class RotaItem : CalendarBaseItem
{
    public string RotaName { get; private set; }
    private bool spansTwoDays;

    public bool spanTwoDays()
    {
        return this.spansTwoDays;
    }

}

the other classes also add there own logic etc. 其他类也添加了自己的逻辑等。

I have a collection of CalendarBaseItem for my day class: 我有一个日历类的CalendarBaseItem集合:

List<CalendarBaseItem> calendarItems;

but on reviewing this I can see that I am breaking LSP principles as I have to check and cast each concrete type to get at the functionality that I desire for each subclass. 但是在回顾这个问题时,我可以看到我正在破坏LSP原则,因为我必须检查并转换每个具体类型以获得我希望每个子类的功能。

I would be grateful if someone could advise how to avoid this problem. 如果有人可以建议如何避免这个问题,我将不胜感激。 Should I use a composition approach and add a CalendarItem class to each of the final classes eg 我应该使用合成方法并为每个最终类添加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;
    }

}

The only problem here is that I will then need a seperate collection for each Concrete CalendarItem in my Day class? 这里唯一的问题是,我将在我的Day类中为每个Concrete CalendarItem需要一个单独的集合?

I think what you're encountering is not so much a Liskov Substitution Principle violation as you are encountering a polymorphism limitation in most languages. 我认为你遇到的不是Liskov替换原则违规,因为你在大多数语言中遇到了多态限制。

With something like List<CalendarBaseItem> the compiler is inferring that you're only dealing with CalendarBaseItem which obviously can't be true if CalendarBaseItem is abstract--but that's what a strongly-typed language does: It's only been told about CalendarBaseItem so that's what it limits usage to. 使用类似List<CalendarBaseItem>的东西,编译器推断你只处理CalendarBaseItem ,如果CalendarBaseItem是抽象的,那么显然不可能是真的 - 但这就是强类型语言的作用:它只被告知CalendarBaseItem所以它是是什么限制了使用。

There are patterns that allow you to deal with this sort of limitation. 有些模式可以让您处理这种限制。 The most popular is the double-dispatch pattern: a specialization of multiple dispatch that dispatches method calls to the run-time type. 最流行的是双分派模式:多分派的特化,它将方法调用分派给运行时类型。 This can be accomplished by providing an override, that when dispatched, dispatches the intended method. 这可以通过提供覆盖来实现,该覆盖在调度时调度预期的方法。 (ie "double dispatch"). (即“双重派遣”)。 It's hard to associate exactly to your circumstances because of the lack of detail. 由于缺乏细节,很难准确地与您的情况相关联。 But, if you wanted to do some processing based on some sort of other type for example: 但是,如果你想根据某种其他类型进行一些处理,例如:

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)
    {
        //...
    }
}

which would replace something like: 这会取代像:

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

Now, that's the "classical" way of doing in in C#--which spans all versions of C#. 现在,这是C#中“经典”的做法 - 跨越所有版本的C#。 With C# 4 the dynamic keyword was introduced to allow run-time type evaluation. 使用C#4引入了dynamic关键字以允许运行时类型评估。 So, you could do what you want without having to write the double-dispatch yourself simply by casting your item to dynamic . 所以,你可以做你想做的事,而不必简​​单地通过将你的项目转换为dynamic来自己编写双重调度。 Which forces the method evaluation to occur at run-time and thus will chose the specialized override: 这迫使方法评估在运行时发生,因此将选择专门的覆盖:

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

This introduces potential run-time exceptions that you'd likely want to catch and deal with--which is why some people don't like this so much. 这引入了您可能想要捕获和处理的潜在运行时异常 - 这就是为什么有些人不喜欢这样的原因。 It's also currently very slow in comparison, so it's not recommended in tight loops that are performance sensitive. 相比之下它目前也很慢,所以不建议在性能敏感的紧密循环中使用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM