Это не обязательно более уродливо, чем альтернатива, которая может повторять код.
В идеальном мире вы сможете моделировать, используя только интерфейсы. например:
Vehicel -> Автомобиль -> Понтиак.
Но может быть логика, которая одинакова для всех Vehicels, поэтому интерфейс не подходит. И у вас нет логики, специфичной для автомобилей. Но вам нужна автомобильная абстракция, потому что ваш TrafficLightController хочет различать автомобили и велосипеды.
В таком случае вам нужно сделать и абстрагировать Автомобиль.
Или вы можете создать интерфейс Vehicle, VehicleImpl реализует Vehicle, интерфейс Car расширяет Vehicle, интерфейс Pontiac реализует Car, а PontiacImpl реализует Pontiac расширяет VehicleImpl. Мне лично не нравится параллельная иерархия интерфейсов для предотвращения пустого абстрактного класса больше, чем пустого абстрактного класса.
Так что я думаю, это вопрос вкуса.
Одна оговорка; если вы используете много прокси-классов, таких как Spring и некоторые тестовые среды, иерархия параллельного интерфейса вполне может привести к менее неожиданным ошибкам.