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