У подклассов есть свои преимущества, но есть и некоторые недостатки. Как правило, я стараюсь избегать наследования реализации и вместо этого использую наследование и делегирование интерфейса.
Одна из причин, по которой я это делаю, заключается в том, что когда вы наследуете реализацию, вы можете столкнуться с проблемами, если переопределите методы, но не будете придерживаться их (иногда недокументированный контракт). Кроме того, я нахожу иерархии классов с трудоемким наследованием реализацией трудными, поскольку методы могут быть переопределены или реализованы на любом уровне. Наконец, при создании подклассов вы можете только расширить интерфейс, вы не можете сузить его. Это приводит к неплотным абстракциям. Хорошим примером этого является java.util.Stack, который расширяет java.util.Vector. Я не должен быть в состоянии рассматривать стек как вектор. Это позволяет потребителю только бегать по интерфейсу.
Другие упоминали принцип замещения Лискова. Я думаю, что использование этого, безусловно, решило бы проблему java.util.Stack, но это также может привести к очень глубокой иерархии классов, чтобы гарантировать, что классы получат только те методы, которые должны иметь.
Вместо этого, с наследованием интерфейса, по существу, отсутствует иерархия классов, потому что интерфейсам редко нужно расширять друг друга. Классы просто реализуют необходимые им интерфейсы и, следовательно, могут правильно обрабатываться потребителем. Кроме того, поскольку наследование реализации отсутствует, потребители этих классов не будут определять свое поведение из-за предыдущего опыта работы с родительским классом.
В конце концов, не имеет значения, по какому пути вы идете. Оба вполне приемлемы. На самом деле это больше зависит от того, с чем вам удобнее работать, и с какими платформами вы работаете. Как гласит старая поговорка: «Когда в Риме делай, как римляне».