Рубрики
Без рубрики

Шаблон проектирования конструктора в Java

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

Автор оригинала: Guest Contributor.

Вступление

В этой статье мы разберем шаблон проектирования Builder и покажем его применение на Java.

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

Знание абстракции, наследования и полиморфизма не обязательно делает вас хорошим объектно-ориентированным дизайнером “из коробки”. Эксперт по дизайну создает проекты, которые удобны в обслуживании и гибки, но самое главное – понятны.

Хорошая идея, связанная с изобретателем, не такая уж хорошая идея.

Шаблоны Творческого Проектирования

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

Эти шаблоны управляют тем, как мы определяем и проектируем объекты, а также тем, как мы их создаем. Некоторые инкапсулируют логику создания отдельно от пользователей и обрабатывают создание ( Фабрика и Абстрактная фабрика ), некоторые фокусируются на процессе создания самих объектов ( Конструктор ), некоторые минимизируют стоимость создания ( Прототип), а некоторые контролируют количество экземпляров во всей JVM ( Синглтон ).

В этой статье мы погрузимся в шаблон проектирования Builder .

Шаблон Проектирования Конструктора

Определение

Шаблон проектирования Builder отделяет построение сложного объекта от его представления. Это делается с помощью вложенного статического класса, который присваивает необходимые значения до возврата экземпляра.

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

Мотивация

Создание экземпляра объекта в Java очень просто. Мы используем ключевое слово new , за которым следует конструктор и параметры, которые мы назначаем объекту. Типичный экземпляр может выглядеть так:

Cookie chocolateChip = new Cookie("Chocolate Chip Cookie");

Строка передается конструктору, и без просмотра определения класса довольно очевидно, что она представляет тип/имя файла cookie.

Хотя, если мы хотим создать экземпляр более сложного класса, такого как нейронная сеть, в этом стиле, мы столкнемся с:

SingleLayerNetwork configuration = new NeuralNetConfiguration(4256, STOCHASTIC_GRADIENT_DESCENT,
                                                              new Adam(), 1e-4, numRows*numColumns,
                                                              1000, RELU, XAVIER);

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

Или еще лучше, представьте, что вам нужно вызвать конструктор этого класса, чтобы создать его экземпляр:

public class SmartHome {
    private String name;
    private int serialNumber;
    private String addressName;
    private String addressNumber;
    private String city;
    private String country;
    private String postalCode;
    private int light1PortNum;
    private int light2PortNum;
    private int door1PortNum;
    private int door2PortNum;
    private int microwavePortNum;
    private int tvPortNum;
    private int waterHeaterPortNum;

    public SmartHome(String name, int serialNumber, String addressName, String addressNumber, String city, String country, String postalCode, int light1PortNum, int light2PortNum, int door1PortNum, int door2PortNum, int microwavePortNum, int tvPortNum, int waterHeaterPortNum) {
        // Assigning values in the constructor call
    }

    // Getters and Setters
}

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

Также обратите внимание, что в Java не принимаются два конструктора с одинаковым типом параметров, но с разными именами переменных.

Наличие этих двух конструкторов не допускается в Java, так как компилятор не может отличить их друг от друга:

public SmartHome(int door1PortNum) { ... }
public SmartHome(int door2PortNum) { ... }

Даже если у нас есть один конструктор с типом параметра int :

public SmartHome(int portNum) { ... }

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

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

Это где появляется шаблон строителя:

Шаблон построителя отделяет конструкцию от представления.

Что это значит?

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

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

Разделив эти два, мы можем сделать представление класса намного проще, аккуратнее и читабельнее, в то время как конструктор выполняет свою часть работы.

Реализация

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

  • Класс статический конструктор должен быть вложен в наш Умный дом класс
  • Конструктор Умный дом должен быть частным , чтобы конечный пользователь не мог его вызвать
  • Класс builder должен иметь интуитивно понятное имя, например Конструктор умного дома
  • Класс Конструктор умного дома будет иметь те же поля, что и класс Умный дом
  • Поля в классе Умный дом могут быть окончательными или нет, в зависимости от того, хотите ли вы, чтобы они были неизменяемыми или нет
  • Класс Конструктор умного дома будет содержать методы, задающие значения, аналогичные методам установщика. Эти методы будут использовать Конструктор интеллектуального дома в качестве возвращаемого типа, присваивать переданные значения полям класса статического построителя и следовать соглашению об именовании построителя. Обычно они начинаются с с , в , в и т. Д. Вместо set .
  • Класс статического построителя будет содержать метод build () , который вводит эти значения в Умный дом и возвращает его экземпляр.

С учетом сказанного, давайте реализуем шаблон компоновщика в нашем примере класса:

public class SmartHome {
    // Fields omitted for brevity
    // The same fields should be in `SmartHome` and `SmartHomeBuilder`

    // Private constructor means we can't instantiate it
    // by simply calling `new SmartHome()`
    private SmartHome() {}

    public static class SmartHomeBuilder {
        private String name;
        private int serialNumber;
        private String addressName;
        private String addressNumber;
        private String city;
        private String country;
        private String postalCode;
        private int light1PortNum;
        private int light2PortNum;
        private int door1PortNum;
        private int door2PortNum;
        private int microwavePortNum;
        private int tvPortNum;
        private int waterHeaterPortNum;

        public SmartHomeBuilder withName(String name) {
            this.name = name;
            return this;
        }

        public SmartHomeBuilder withSerialNumber(int serialNumber) {
            this.serialNumber = serialNumber;
            return this;
        }

        public SmartHomeBuilder withAddressName(String addressName) {
            this.addressName = addressName;
            return this;
        }

        public SmartHomeBuilder inCity(String city) {
            this.city = city;
            return this;
        }

        public SmartHomeBuilder inCountry(String country) {
            this.country = country;
            return this;
        }

        // The rest of the methods are omitted for brevity
        // All follow the same principle

        public SmartHome build() {
            SmartHome smartHome = new SmartHome();
            smartHome.name = this.name;
            smartHome.serialNumber = this.serialNumber;
            smartHome.addressName = this.addressName;
            smartHome.city = this.city;
            smartHome.country = this.country;
            smartHome.postalCode = this.postalCode;
            smartHome.light1PortNum = this.light1PortNum;
            smartHome.light2PortNum = this.light2PortNum;
            smartHome.door1PortNum = this.door1PortNum;
            smartHome.door2PortNum = this.door2PortNum;
            smartHome.microwavePortNum = this.microwavePortNum;
            smartHome.tvPortNum = this.tvPortNum;
            smartHome.waterHeaterPortNum = this.waterHeaterPortNum;

            return smartHome;
        }
    }
}

Класс SmartHome не имеет общедоступных конструкторов, и единственный способ создать объект Умный дом – это использовать класс SmartHomeBuilder , например:

SmartHome smartHomeSystem = new SmartHome
    .SmartHomeBuilder()
    .withName("RaspberrySmartHomeSystem")
    .withSerialNumber(3627)
    .withAddressName("Main Street")
    .withAddressNumber("14a")
    .inCity("Kumanovo")
    .inCountry("Macedonia")
    .withPostalCode("1300")
    .withDoor1PortNum(342)
    .withDoor2PortNum(343)
    .withLight1PortNum(211)
    .withLight2PortNum(212)
    .withMicrowavePortNum(11)
    .withTvPortNum(12)
    .withWaterHeaterPortNum(13)
    .build();

System.out.println(smartHomeSystem);

Хотя мы усложнили сам класс, включив вложенный класс с повторяющимися полями, представление отделено от создания.

Очевидно, что мы создаем при создании экземпляра объекта. Это читабельно, понятно, и любой может использовать ваши классы для создания объектов.

Возвращаясь к примеру с реальной нейронной сетью, это выглядело бы примерно так:

MultiLayerNetwork conf = new NeuralNetConfiguration.Builder()
    .seed(rngSeed)
    .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
    .updater(new Adam())
    .l2(1e-4)
    .list()
    .layer(new DenseLayer.Builder()
        .nIn(numRows * numColumns) // Number of input datapoints.
        .nOut(1000) // Number of output datapoints.
        .activation(Activation.RELU) // Activation function.
        .weightInit(WeightInit.XAVIER) // Weight initialization.
        .build())
    .layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .nIn(1000)
        .nOut(outputNum)
        .activation(Activation.SOFTMAX)
        .weightInit(WeightInit.XAVIER)
        .build())
    .pretrain(false).backprop(true)
    .build()

Кодовый Кредит: DeepLearning4j – Быстрый старт

плюсы и минусы

Помимо наиболее очевидного смысла использования шаблона Builder, есть еще несколько плюсов, которые могут быть не слишком очевидными на первый взгляд:

  • Вы можете изменить реализацию объекта в любом случае, как вам заблагорассудится, и просто обновить методы. Конечный пользователь сталкивается с абстрактным интерфейсом через класс static builder и не интересуется базовой реализацией.
  • Он поддерживает инкапсуляцию, отделяя представление объекта от конструкции.

Единственным реальным недостатком является то, что это увеличивает объем кода в доменных моделях. Они, как правило, уже длинные, хотя и относительно простые (поля, геттеры и сеттеры). Хотя, в любом случае, вы бы редко вмешивались в эти занятия.

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

Вывод

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

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

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