Наследование и принцип подстановки Лискова

Я изо всех сил стараюсь придерживаться принципа подстановки Лискова при создании структуры моего класса. Я хочу, чтобы коллекция элементов календаря хранилась в дневном классе. Должно быть несколько разных типов 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;
    }

}

Единственная проблема здесь в том, что мне понадобится отдельная коллекция для каждого конкретного элемента CalendarItem в моем классе Day?

1 ответ

Решение

Я думаю, что вы сталкиваетесь не столько с нарушением принципа подстановки Лискова, сколько с ограничением полиморфизма в большинстве языков.

С чем-то вроде 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);

Это вводит потенциальные исключения во время выполнения, которые вы, вероятно, захотите поймать и с которыми справиться - вот почему некоторым людям это так не нравится. В настоящее время он также очень медленный в сравнении, поэтому его не рекомендуется использовать в тесных циклах, чувствительных к производительности.

Другие вопросы по тегам