Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Java. Шаблон Builder

Шаблон не является частью языка Java, это рекомендация умных программистов для создания эффективных решений во время разработки сложных проектов.

Если ваш класс содержит большое количество конструкторов и статических методов, то возникает проблема - трудно запомнить порядок и назначение необязательных параметров при вызове класса. В таких случаях рекомендуется использовать шаблон Builder.

Создадим для примера "плохой" класс с двумя обязательными и с четырьмя необязательными параметрами.


public class BadClass {
    private final int reqFieldOne;
    private final int reqFieldTwo;
    private final int optFieldOne;
    private final int optFieldTwo;
    private final int optFieldThird;
    private final int optFieldFour;

    public BadClass(int reqFieldOne, int reqFieldTwo) {
        this(reqFieldOne, reqFieldTwo, 0);
    }

    public BadClass(int reqFieldOne, int reqFieldTwo,
                    int optFieldOne) {
        this(reqFieldOne, reqFieldTwo, optFieldOne, 0);
    }

    public BadClass(int reqFieldOne, int reqFieldTwo,
                    int optFieldOne, int optFieldTwo) {
        this(reqFieldOne, reqFieldTwo, optFieldOne, optFieldTwo, 0);
    }

    public BadClass(int reqFieldOne, int reqFieldTwo,
                    int optFieldOne, int optFieldTwo, int optFieldThird) {
        this(reqFieldOne, reqFieldTwo, optFieldOne, optFieldTwo, optFieldThird, 0);
    }

    public BadClass(int reqFieldOne, int reqFieldTwo,
                    int optFieldOne, int optFieldTwo, int optFieldThird, int optFieldFour) {
        this.reqFieldOne = reqFieldOne;
        this.reqFieldTwo = reqFieldTwo;
        this.optFieldOne = optFieldOne;
        this.optFieldTwo = optFieldTwo;
        this.optFieldThird = optFieldThird;
        this.optFieldFour = optFieldFour;
    }
}

Создание экземпляра класса с использованием конструктора со всем набором параметров.


BadClass badClass = new BadClass(3, 3, 3, 3, 4, 5);

Глядя на набор чисел, трудно понять код. Правда, на данный момент студия выводит подсказку прямо в редакторе, что упростило задачу. Но этот же код, опубликованный на сайте, не будет содержать подобных подсказок, поэтому проблема остаётся. Кроме того, увеличение числа параметров только усложняет сопровождение кода.

Можно прибегнуть к другому варианту - вызвать конструктор без параметров, чтобы создать объект, а затем вызывать сеттеры для установки обязательных и некоторых необязательных параметров.


public class BadClass {
    // инициализируем некоторые параметры значениями по умолчанию
    private int reqFieldOne = -1;
    private int reqFieldTwo = -1;
    private int optFieldOne = 0;
    private int optFieldTwo = 0;
    private int optFieldThird = 0;
    private int optFieldFour = 0;

    public BadClass() {
    }

    // Сеттеры
    public void setReqFieldOne(int val) {
        reqFieldOne = val;
    }

    public void setReqFieldTwo(int val) {
        reqFieldTwo = val;
    }

    public void setOptFieldOne(int optFieldOne) {
        this.optFieldOne = optFieldOne;
    }

    public void setOptFieldTwo(int optFieldTwo) {
        this.optFieldTwo = optFieldTwo;
    }

    public void setOptFieldThird(int optFieldThird) {
        this.optFieldThird = optFieldThird;
    }

    public void setOptFieldFour(int optFieldFour) {
        this.optFieldFour = optFieldFour;
    }
}

Количество кода в классе увеличивается, но код стал более читаемым при создании экземпляра класса.


BadClass badClass = new BadClass();
badClass.setReqFieldOne(10);
badClass.setReqFieldTwo(30);
badClass.setOptFieldOne(20);
// и т.д.

Но у этого подхода также есть недостатки. Класс находится в неустойчивом состоянии, так как зависит от множества отдельных вызовов методов и не может обеспечить проверку своего состояния.

Поэтому был придуман третий вариант, который назвали шаблоном Builder. Вместо непосредственного создания объекта класса вызывается конструктор (или статический метод) со всеми необходимыми параметрами, чтобы получить объект Builder. Затем вызываются сеттеры для установки всех необходимых параметров. В завершение, вызывается метод build() для генерации объекта, который будет являться неизменным. Реализуется такой подход через статический внутренний класс.

Создадим уже "хороший" класс с применением Builder.


public class GoodClass {
    private final int reqFieldOne;
    private final int reqFieldTwo;
    private final int optFieldOne;
    private final int optFieldTwo;
    private final int optFieldThird;
    private final int optFieldFour;

    public static class Builder {
        // Обязательные параметры
        private final int reqFieldOne;
        private final int reqFieldTwo;

        // Необязательные параметры с значениями по умолчанию
        private int optFieldOne = 0;
        private int optFieldTwo = 0;
        private int optFieldThird = 0;
        private int optFieldFour = 0;

        public Builder(int reqFieldOne, int reqFieldTwo) {
            this.reqFieldOne = reqFieldOne;
            this.reqFieldTwo = reqFieldTwo;
        }

        public Builder optFieldOne(int val) {
            optFieldOne = val;
            return this;
        }

        public Builder optFieldTwo(int val) {
            optFieldTwo = val;
            return this;
        }

        public Builder optFieldThird(int val) {
            optFieldThird = val;
            return this;
        }

        public Builder optFieldFour(int val) {
            optFieldFour = val;
            return this;
        }

        public GoodClass buidl() {
            return new GoodClass(this);
        }
    }

    private GoodClass(Builder builder) {
        reqFieldOne = builder.reqFieldOne;
        reqFieldTwo = builder.reqFieldTwo;
        optFieldOne = builder.optFieldOne;
        optFieldTwo = builder.optFieldTwo;
        optFieldThird = builder.optFieldThird;
        optFieldFour = builder.optFieldFour;
    }
}

Класс GoodClass является неизменным и все значения параметров по умолчанию находятся в одном месте. Сеттеры возвращают класс-строитель, поэтому вызовы можно объявлять в цепочку.


GoodClass goodClass = new GoodClass.Builder(40, 20)
        .optFieldOne(2)
        .optFieldTwo(4)
        .optFieldThird(23)
        .optFieldFour(9)
        .buidl();

При таком подходе код проще писать и легко читать. У шаблона есть и другие преимущества, например, можно задействовать несколько параметров varags.

Недостатком шаблона является затраты на создание класса Builder, но если вы будете позже расширять класс, то подход оправдает себя.

Создавайте Builder в том случае, когда параметров четыре и больше. В остальных случаях используйте традиционный подход.

В Android часто используется шаблон Builder, например, AlertDialog.Builder, Notification.Builder, JobInfo.Builder, RoomDatabase.Builder.

Реклама