Є два способи використання абстрактних базових класів.
Ви спеціалізуєтеся на своєму абстрактному об'єкті, але всі клієнти будуть використовувати похідний клас через його базовий інтерфейс.
Ви використовуєте абстрактний базовий клас, щоб виділити дублювання в об'єктах у вашому дизайні, а клієнти використовують конкретні реалізації через власні інтерфейси!
Рішення для 1 - стратегія
Якщо у вас є перша ситуація, ви фактично маєте інтерфейс, визначений віртуальними методами в абстрактному класі, який реалізують ваші похідні класи.
Вам слід розглянути можливість створення справжнього інтерфейсу, змінити абстрактний клас на конкретний, і взяти примірник цього інтерфейсу в його конструкторі. Потім отримані класи стануть реалізацією цього нового інтерфейсу.
Це означає, що тепер ви можете протестувати свій раніше абстрактний клас, використовуючи макетний екземпляр нового інтерфейсу та кожну нову реалізацію через теперішній загальнодоступний інтерфейс. Все просто і перевіряється.
Рішення для 2
Якщо у вас є друга ситуація, то ваш абстрактний клас працює як помічник.
Погляньте на функціонал, який він містить. Подивіться, чи можна будь-який із них натиснути на об'єкти, якими маніпулюють, щоб мінімізувати це дублювання. Якщо у вас все ще залишається, погляньте на те, щоб зробити його допоміжним класом, який ваша конкретна реалізація бере у своєму конструкторі, і видаліть їх базовий клас.
Це знову ж таки призводить до конкретних занять, які прості та легко перевіряються.
Як правило
Віддавайте перевагу складній мережі простих об'єктів над простою мережею складних об'єктів.
Ключ до розширюваного тестового коду - це невеликі будівельні блоки та незалежна електропроводка.
Оновлено: Як обробляти суміші обох?
Можна мати базовий клас, який виконує обидві ці ролі ... тобто: він має загальнодоступний інтерфейс і захищені допоміжні методи. Якщо це так, то ви можете розподілити допоміжні методи в один клас (сценарій2) і перетворити дерево спадкування в шаблон стратегії.
Якщо ви виявите, що у вас є деякі методи, які ваш базовий клас реалізує безпосередньо, а інші - віртуальні, тоді ви все одно можете перетворити дерево спадкування в шаблон стратегії, але я також вважаю це хорошим показником того, що обов'язки не вирівняні правильно, і, можливо, потребують рефакторингу.
Оновлення 2: Абстрактні класи як трамплін (2014/06/12)
Днями у мене була ситуація, коли я використовував конспект, тому хотів би вивчити чому.
Для наших конфігураційних файлів у нас є стандартний формат. Цей конкретний інструмент має 3 файли конфігурації, всі у цьому форматі. Я хотів, щоб сильно набрали клас для кожного файла налаштувань, щоб через введення залежності клас міг попросити налаштування, про які він піклувався.
Я реалізував це, маючи абстрактний базовий клас, який знає, як проаналізувати формати файлів налаштувань та похідні класи, які відкрили ті самі методи, але інкапсулював розташування файлу налаштувань.
Я міг би написати "SettingsFileParser", який три класи обернули, а потім делегували до базового класу, щоб відкрити методи доступу до даних. Я вирішив поки що цього не робити, оскільки це призведе до 3 похідних класів з більшою кількістю коду делегації, ніж будь-що інше.
Однак ... у міру того, як цей код розвивається і споживачі кожного з цих класів налаштувань стають зрозумілішими. Кожен користувач налаштувань запитає деякі параметри та певним чином перетворить їх (оскільки налаштування є текстовими, вони можуть обернути їх в об'єкти перетворення їх у числа тощо). Як це станеться, я почну видобувати цю логіку в методах обробки даних і відштовхувати їх на сильно набрані класи налаштувань. Це призведе до вищого рівня інтерфейсу для кожного набору налаштувань, який з часом вже не знає, що він має справу з "налаштуваннями".
На даний момент сильно набрані класи налаштувань більше не потребуватимуть методів "getter", які розкривають основні "налаштування".
У той момент я більше не хотів би, щоб їх публічний інтерфейс включав методи доступу аксесуарів; тому я зміню цей клас, щоб інкапсулювати клас синтаксичного аналізу, а не виходити з нього.
Отже, клас "Анотація" - це спосіб мені уникнути делегування коду на даний момент та маркер у коді, який нагадує мені пізніше змінити дизайн. Я ніколи не можу дістатись до нього, тому він може прожити добрий час ... тільки код може розказати.
Я вважаю це правдою з будь-яким правилом ... як "немає статичних методів" або "немає приватних методів". Вони вказують запах у коді ... і це добре. Це змушує вас шукати абстракцію, яку ви пропустили ... і дозволяє продовжувати надавати цінність для свого клієнта в середній час.
Я уявляю такі правила, як цей визначає ландшафт, де в долинах живе стійкий код. Коли ви додаєте нову поведінку, це як дощ на посадці на ваш код. Спочатку ви ставите його там, де він приземляється .. потім ви рефактор, щоб дозволити силам гарного дизайну підштовхувати поведінку навколо, поки все не опиниться в долинах.