Как избежать дублирования вызовов подкласса при использовании композиции?

Jonathan спросил: 03 февраля 2018 в 01:18 в: oop

Итак, я склоняюсь к композиции над наследованием, и я бы хотел, чтобы ответы на неследования на этот вопрос были.

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

public interface IChemistry
{
    void SeparateAtom(Atom atom);
    void BreakBond(Bond bond);
}public class BaseChemistry : IChemistry
{
    public void SeparateAtom(Atom atom)
    {
        //possible extra logic here
        for(int i=0;i < atom.BondCount;i++)
        {
            //maybe extra logic here etc.
            BreakBond(atom.Bonds[i]);
        }
    }    public void BreakBond(Bond bond)
    {
        //do some bond breaking logic here
    }
}public class RealisticChemistry : IChemistry
{
    private BaseChemistry base;    public RealisticChemistry(BaseChemistry base)
    {
        this.base = base;
    }
    public void SeparateAtom(Atom atom)
    {
        //subclass specific logic here perhaps
        base.SeparateAtom(atom);
    }    public void BreakBond(Bond bond)
    {
        //more subclass specific logic
        base.BreakBond(bond);
    }
}

Как вы можете видеть в этом проекте, есть вопиющая проблема. Когда вызывается метод подкласса SeparateAtom(), он выполняет часть своей собственной логики и затем передает остальные в базовый класс, который затем вызывает метод BreakBond() в базовом классе, а не на подкласс.

Существуют различные решения, которые я могу придумать для этого, и почти все из них имеют довольно существенные неудачи:

  • Скопируйте и вставьте. Наихудшим вариантом в этом случае было бы просто скопировать цикл (и дополнительную логику) в методе базового класса SeparateAtom(), в подкласс "один". Я не чувствую, что необходимо объяснить, почему копирование и вставка - не лучшая практика. Другой вариант может заключаться в том, чтобы упаковать некоторую дополнительную логику вокруг цикла в дополнительные методы, так что это всего лишь цикл, который копируется. Но призывы к дополнительным методам все еще копируются, а разбивка на несколько методов может разрушить инкапсуляцию. Например, что, если часть этой логики зависит от конкретного контекста SeparateAtom() и может привести к ошибочным данным, если вызываемый вне контекста кто-то, кто не знает код очень хорошо?
  • Слушайте или наблюдайте события разрыва в базовом классе. Это решение кажется мне проблематичным, потому что путь расширения функциональности базового класса становится неясным. Например, без предварительного знания, если нужно попытаться расширить класс, они могут интуитивно реализовать проект выше и интерпретировать слушателя как необязательный, когда это действительно необходимо, если вы хотите расширить поведение нарушения связи.
  • Для базового класса требуется делегат. Например, базовому классу может потребоваться ссылка на IBondBreakDelegate, который вызывается внутри BondBreak(). Аналогичная проблема с подходом слушателя связана с тем, что смесь состава и других подходов делает невозможным предполагаемое использование базового класса. Кроме того, несмотря на то, что теперь есть действительно необходимый делегат, что делает предполагаемое использование более понятным, базовый класс теперь больше не может функционировать сам по себе. Также, если нужно расширить иерархию с дополнительным подклассом (например, public class MoreRealistiChemistry и т. Д.), Как можно было бы расширить делегированное поведение посредством композиции?
  • Делегировать все вместо компоновки. Я бы предпочел не идти по этому маршруту, потому что, когда классы нуждаются в дополнительной функциональности, количество необходимых делегатов увеличивается (или количество методов в делегатах). Также, если некоторые из делегированных действий являются необязательными? Тогда либо для каждого поведения, который реализуется подклассом, либо для каждого поведения должны быть отдельные необязательные делегаты, или вы получаете множество пустых тел метода в подклассе.

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

0 ответов