Есть два способа использования абстрактных базовых классов.
Вы специализируете свой абстрактный объект, но все клиенты будут использовать производный класс через его базовый интерфейс.
Вы используете абстрактный базовый класс, чтобы исключить дублирование объектов в вашем проекте, а клиенты используют конкретные реализации через свои собственные интерфейсы.
Решение для 1 - шаблон стратегии
Если у вас первая ситуация, то у вас фактически есть интерфейс, определенный виртуальными методами в абстрактном классе, которые реализуют ваши производные классы.
Вам следует подумать о том, чтобы сделать это реальным интерфейсом, изменить конкретный класс на конкретный, и взять экземпляр этого интерфейса в его конструкторе. Затем ваши производные классы становятся реализациями этого нового интерфейса.
Это означает, что теперь вы можете протестировать свой ранее абстрактный класс, используя фиктивный экземпляр нового интерфейса, а каждую новую реализацию - через общедоступный интерфейс. Все просто и проверяемо.
Решение для 2
Если у вас вторая ситуация, тогда ваш абстрактный класс работает как вспомогательный класс.
Посмотрите на функциональность, которую он содержит. Посмотрите, может ли что-либо из этого быть помещено на объекты, которыми манипулируют, чтобы минимизировать это дублирование. Если у вас все еще есть что-то, посмотрите, как сделать это вспомогательным классом, который ваша конкретная реализация использует в своем конструкторе, и удалите его базовый класс.
Это снова приводит к конкретным классам, которые просты и легко тестируемы.
Как правило
Пользуйтесь сложной сетью простых объектов над простой сетью сложных объектов.
Ключ к расширяемому тестируемому коду - небольшие строительные блоки и независимая проводка.
Обновлено: как обрабатывать смеси обоих?
Можно иметь базовый класс, выполняющий обе эти роли ... т.е. он имеет открытый интерфейс и имеет защищенные вспомогательные методы. Если это так, то вы можете выделить вспомогательные методы в один класс (script2) и преобразовать дерево наследования в шаблон стратегии.
Если вы обнаружите, что у вас есть некоторые методы, которые ваш базовый класс реализует напрямую, а другие являются виртуальными, то вы все равно можете преобразовать дерево наследования в шаблон стратегии, но я бы также использовал его как хороший индикатор того, что обязанности не правильно выровнены, и может нужен рефакторинг.
Обновление 2: Абстрактные классы как трамплин (2014/06/12)
У меня была ситуация, когда я использовал реферат, поэтому я хотел бы выяснить, почему.
У нас есть стандартный формат для наших файлов конфигурации. Этот конкретный инструмент имеет 3 файла конфигурации в этом формате. Я хотел иметь строго типизированный класс для каждого файла настроек, чтобы через внедрение зависимостей класс мог запрашивать настройки, о которых он заботился.
Я реализовал это, имея абстрактный базовый класс, который знает, как анализировать форматы файлов настроек и производные классы, которые предоставляют те же методы, но инкапсулируют местоположение файла настроек.
Я мог бы написать «SettingsFileParser», который обернул 3 класса, а затем делегировать его базовому классу для предоставления методов доступа к данным. Я решил не делать этого пока , как это привело бы к 3 производных классов с более делегирования кода в них , чем все остальное.
Однако ... как этот код развивается, и потребители каждого из этих классов настроек становятся понятнее. Каждую настройку пользователи будут запрашивать некоторые настройки и каким-то образом преобразовывать их (поскольку настройки являются текстовыми, они могут обернуть их в объекты, преобразовать их в числа и т. Д.). Когда это произойдет, я начну извлекать эту логику в методы манипулирования данными и возвращать их обратно в строго типизированные классы настроек. Это приведет к более высокоуровневому интерфейсу для каждого набора настроек, который в конечном итоге перестанет осознавать, что имеет дело с «настройками».
На этом этапе строго типизированные классы настроек больше не будут нуждаться в методах «получения», которые предоставляют базовую реализацию «настроек».
В этот момент я бы больше не хотел, чтобы их общедоступный интерфейс включал методы доступа к настройкам; поэтому я изменю этот класс, чтобы инкапсулировать класс анализатора настроек, а не наследовать от него.
Таким образом, класс Abstract: для меня способ избежать кода делегирования на данный момент и маркер в коде, чтобы напомнить мне о необходимости изменить дизайн позже. Я, возможно, никогда не доберусь до этого, поэтому он может жить долго ... только код может сказать.
Я считаю, что это верно для любого правила ... типа "нет статических методов" или "нет частных методов". Они указывают на запах в коде ... и это хорошо. Это заставляет вас искать абстракцию, которую вы пропустили ... и позволяет в то же время обеспечивать ценность для вашего клиента.
Я представляю себе правила, подобные этому, определяющие ландшафт, в котором обслуживаемый код живет в долинах. Когда вы добавляете новое поведение, это похоже на дождь в вашем коде. Сначала вы помещаете его туда, где он приземляется. Затем вы делаете рефакторинг, чтобы позволить силам хорошего дизайна изменить поведение, пока все не окажется в долинах.