Компромиссы в вариантах, которые вы предоставили, действительно сводятся к мерам удобства и общения. Вы должны рассматривать свой подход в контексте того, с чем разработчик столкнется (рефакторинг) через 6 месяцев, когда он реализует новый подкласс или рефакторинг существующей работы.
Если вы поместите конкретную реализацию в свой базовый класс, вы фактически скажете: «Это поведение по умолчанию», и все классы, которые переопределяют этот метод, являются исключениями из правила. Имхо, это рискованно, так как его можно не заметить и привести к ошибкам, которые трудно отследить на некоторых платформах, таких как ruby, scala или даже python. Есть хороший аргумент в пользу того, что методы абстрактных классов должны быть запечатаны везде, где это возможно.
Если оставить метод абстрактным, это означает, что намерение и использование открыты для интерпретации наследующим классом. Этот подход можно дополнить использованием статических функций или вспомогательных библиотек.
Гибридный подход, который я использовал, состоит в том, чтобы базовая реализация выдавала исключение при вызове, сигнализируя разработчику, что от него требуется предоставить реализацию, и в то же время не слишком останавливая разработку.
За свои деньги я предпочитаю последние случаи. Это требует от разработчика тщательного обдумывания того, чего на самом деле должен достичь метод, и помогает устранить любые «сюрпризы», которые могут возникнуть, например. «Я не знал, что это так назовется!». Независимо от вашего подхода, обязательно используйте разумное применение TDD. Не только для того, чтобы убедиться, что ваша логика работает, но и для того, чтобы действовать как отказоустойчивость, если логика в другом месте изменится, нарушив ваш шаблон.
Прикрепляю к посту несколько видео по теме: