Подтвердить что ты не робот

Howto дизайн для расширения

Существует Checkstyle правило DesignForExtension. В нем говорится: если у вас есть общедоступный/защищенный метод, который не является абстрактным, ни окончательным, ни пустым, он не "предназначен для расширения". Прочитайте описание этого правила на странице Checkstyle для обоснования.

Представьте себе этот случай. У меня есть абстрактный класс, который определяет некоторые поля и метод проверки для этих полей:

public abstract class Plant {
    private String roots;
    private String trunk;

    // setters go here

    protected void validate() {
        if (roots == null) throw new IllegalArgumentException("No roots!");
        if (trunk == null) throw new IllegalArgumentException("No trunk!");
    }

    public abstract void grow();
}

У меня также есть подкласс Plant:

public class Tree extends Plant {
    private List<String> leaves;

    // setters go here

    @Overrides
    protected void validate() {
        super.validate();
        if (leaves == null) throw new IllegalArgumentException("No leaves!");
    }

    public void grow() {
        validate();
        // grow process
    }
}

Следуя правилу Checkstyle, метод Plant.validate() не предназначен для расширения. Но как я могу создать расширение в этом случае?

4b9b3361

Ответ 1

Правило жалуется, потому что возможно, чтобы производный (расширяющий) класс полностью заменил предоставленную вами функциональность, не сообщая вам об этом. Это убедительное указание на то, что вы не полностью рассмотрели, как этот тип может быть расширен. Вместо этого он хочет сделать следующее:

public abstract class Plant {
    private String roots;
    private String trunk;

    // setters go here

    private void validate() {
        if (roots == null) throw new IllegalArgumentException("No roots!");
        if (trunk == null) throw new IllegalArgumentException("No trunk!");
        validateEx();
    }

    protected void validateEx() { }

    public abstract void grow();
}

Обратите внимание, что теперь кто-то еще может предоставить свой собственный код проверки, но они не могут заменить ваш предварительно написанный код. В зависимости от того, как вы хотели использовать метод validate, вы также могли бы сделать его общедоступным.

Ответ 2

Хотя ответ Джоэла Кохорна объясняет, как преодолеть конкретную проблему, поставленную OP, Id хотел бы предложить подход, который рассматривает более широкий взгляд на "как разработать для расширения? Как отмечает OP в одном из своих комментариев, данное решение не очень хорошо масштабируется с растущей (класс) глубиной наследования. Кроме того, ожидая в базовом классе необходимость проверки допустимых дочерних классов (validateTreeEx()) проблематично по понятным причинам.

Предложение: проверьте свойства растений во время строительства и полностью удалите validate() (вместе с возможными сеттерами, см. также http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). Исходный код предполагает, что validate() является инвариантом, который должен быть истинным перед каждой операцией grow(). Я сомневаюсь, что этот дизайн преднамерен. Если нет операции, которая может "разорвать завод после строительства", нет необходимости повторно проверять достоверность снова и снова.

Идя еще дальше, Id задает вопрос о качестве первоначального дизайна наследования. Без дополнительных (возможно, полиморфных) операций Tree просто повторно использует некоторые свойства Plant. Я придерживаюсь мнения, что наследование класса не должно использоваться для повторного использования кода. Джош Блох говорит об этом (от Effective Java, 2nd Edition, глава 4):

Если вы используете наследование, когда композиция подходит, вы бесполезно раскрывать детали реализации. Полученный API связывает вас к первоначальной реализации, навсегда ограничивающей производительность твой класс. Более серьезно, разоблачая внутренности, вы позволяете клиент обращается к ним напрямую.

Также проверьте "Пункт 17:" Дизайн и документ для наследования или запретите "(также глава 4 для той же книги)