Работа с иерархией наследования из-за принципа Open-Closed
Когда я пытаюсь следовать принципу Открыто-Закрыто (OCP), после ряда реализованных вариантов использования я всегда получаю иерархию унаследованных классов. Обычно это происходит с ViewModel в структуре MVVM, потому что они сильно меняются. Например (C#, но может быть любой другой язык, ориентированный на классы):
internal class MyAwesomeViewModel
{
private IDependency1 _dependency1;
public string Property1 { get; set; }
public ICommand Command1 { get; }
public MyAwesomeViewModel(IDependency1 dependency1)
{
_dependency1 = dependency1;
Command1 = new DelegateCommand(...);
}
}
internal class MyAwesomeViewModelWithAnotherProperty: MyAwesomeViewModel
{
public string Property2 { get; set; }
public MyAwesomeViewModelWithAnotherProperty(IDependency1 dependency1)
:base(dependency1)
{
}
}
internal class MyAwesomeViewModelWithNewCommandAndDependency: MyAwesomeViewModelWithAnotherProperty
{
private IDependency2 _dependency2;
public ICommand Command2;
public MyAwesomeViewModelWithNewCommandAndDependency(IDependency1 dependency1, IDependency2 dependency2)
: base(dependency1)
{
_dependency2 = dependency2;
Command2 = new DelegateCommand(...);
}
}
internal class MyAwesomeViewModelWithBunchNewProperties : MyAwesomeViewModelWithNewCommandAndDependency
{
public int IntProperty { get; }
public bool BoolProperty { get; }
public double DoubleProperty { get; }
public MyAwesomeViewModelWithBunchNewProperties(IDependency1 dependency1, IDependency2 dependency2)
: base(dependency1, dependency2)
{
}
}
Проблема в том, что когда речь идет о глубине наследования 4 и более уровней, она становится очень грязной и трудной для чтения. Также я всегда сталкиваюсь с проблемой именования, которая является сигналом неправильной композиции (например, MainWindowViewModel, затем MainWindowViewModelCloseCommand, затем MainWindowViewModelUserRelatedProperties и так далее).
Поскольку используется только последний производный класс (MyAwesomeViewModelWithBunchNewProperties в приведенном выше примере), иногда я рассматриваю вопрос о том, чтобы объединить все наследство перед выпуском или другой важный этап в 1 класс, например так:
internal class MyAwesomeViewModel
{
public int IntProperty { get; }
public bool BoolProperty { get; }
public double DoubleProperty { get; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public ICommand Command1 { get; }
public ICommand Command2;
public MyAwesomeViewModelWithBunchNewProperties(IDependency1 dependency1, IDependency2 dependency2)
{
_dependency1 = dependency1;
_dependency2 = dependency2;
Command1 = new DelegateCommand(...);
Command2 = new DelegateCommand(...);
}
}
Но это может нарушить принцип единой ответственности (SRP) и привести к очень большому суперклассу.
Вопрос: как бороться с проблемой наследования? Или это совсем не проблема, и все в порядке, если у вас есть такая структура классов?
3 ответа
Композиция по наследству!
Часто разработчики смотрят на принципы, подобные SOLID, и забывают о базовом принципе Composition over the Наследование.
Первое, что нужно запомнить, это то, что наследование тесно связывает вас с базовым классом. Это приводит к таким проблемам, как отказ в завещании (нарушение принципа подстановки Лискова).
Когда мы говорим о классах в ООП, мы определяем поведение, связанное с данными, а не объектами. Когда мы моделируем проблему с точки зрения поведения, которого она пытается достичь, мы можем получить небольшие строительные блоки.
Вы можете определить поведение ядра из MyAwesomeViewModel в крошечные классы, на которые вы можете ссылаться в других ваших классах. Таким образом, вы можете легко составлять объекты, такие как MyAwesomeViewModelWithBunchNewProperties.
Что касается принципа единой ответственности, то это очень неправильно понятый принцип. SRP заявляет, что совместное поведение должно меняться вместе. Это означает, что единый набор поведений, которые зависят друг от друга и будут меняться вместе, принадлежат к одному и тому же классу.
Что касается вашего конкретного сценария, модели представлений часто могут не выиграть от композиции или наследования. Модели представления - это объекты передачи данных (DTO), они не фиксируют поведение. Дублирование кода здесь может быть очень легко пропущено \ приемлемо. Если дублирование кода создает проблему в ваших моделях представлений, просто составьте их из других DTO
Чтобы заставить OCP работать на вас, вы должны определить поведение, которое нужно вашим клиентам. Эти знания могут быть использованы для создания одного или нескольких интерфейсов, которые затем реализуют ваши классы ViewModel. Чтобы избежать иерархии наследования, вы можете предпочесть композицию для структурирования ваших ViewModels
Принципы SOLID - это идеи / рекомендации, которые помогут вам найти лучшие решения. Ничто по сути не получено от следования этим принципам.
Опубликованная здесь стратегия наследования не работает. Это ничего не дает и вызывает путаницу и больше работы. Это не хороший путь.
Но это может нарушить принцип единой ответственности (SRP) и привести к очень большому суперклассу.
SRP очень расплывчато с точки зрения того, что такое "единая ответственность". Вы можете определить это так узко или так широко, как вы хотите. Опять же, принцип заключается в том, чтобы направлять вас и заставлять думать о том, чтобы не смешивать вещи, которые должны быть отделены друг от друга.
Здесь вы можете сказать: "Ответственность этого класса - быть моделью для привязки данных к представлению".
SuperClass
Такой же. Это просто руководство. Вы никогда не можете сказать "класс может иметь не более N членов". Этот совет неверен для любого N, потому что N является контекстно-зависимым.