Хотя в ответе Джоэла Кохорна объясняется, как преодолеть конкретную проблему, опубликованную ОП, я хотел бы предложить подход, который дает более широкий взгляд на «как проектировать для расширения?»
Как указывает ОП в одном из своих комментариев, данное решение плохо масштабируется с растущей (классовой) глубиной наследования. Кроме того, предвидеть в базовом классе необходимость проверки возможных дочерних классов (validateTreeEx()
) проблематично по очевидным причинам.
Предложение: Проверьте свойства растений во время строительства и удалите validate()
в целом (вместе с возможными установщиками; см. Также http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). Оригинальный код предполагает, что validate()
является инвариантом, который должен быть истинным до каждая операция grow()
. Я сомневаюсь, что эта конструкция является преднамеренной. Если нет операции, которая может «сломать» завод после строительства, нет необходимости повторно проверять действительность снова и снова.
Если пойти еще дальше, я бы усомнился в правильности первоначального проекта наследования. Без дополнительных (возможно, полиморфных) операций Tree
просто повторно использует некоторые свойства Plant. Я твердо придерживаюсь мнения, что наследование классов не должно использоваться для повторного использования кода. Джош Блох говорит об этом (из Effective Java, 2nd Edition, глава 4):
Если вы используете наследование там, где подходит композиция, вы
без необходимости выставлять детали реализации. Полученный API связывает вас
к первоначальной реализации, навсегда ограничивая производительность
твой класс. Более серьезно, выставляя внутренние органы, вы позволяете
клиент получает к ним доступ напрямую.
Также ознакомьтесь с «Пунктом 17: Дизайн и документ для наследования или иначе запретите его» (также глава 4 для той же книги)