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

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

Вступление

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

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

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

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

Шаблоны творческого дизайна

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

Эти шаблоны контролируют то, как мы определяем и проектируем объекты, а также то, как мы создаем их экземпляры. Некоторые инкапсулируют логику создания вдали от пользователей и обрабатывают создание ( Factory и Abstract Factory ), некоторые сосредотачиваются на процессе построения самих объектов ( Builder ), некоторые минимизируют стоимость создания ( Prototype ), а некоторые контролируют количество экземпляров на вся JVM ( синглтон ).

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

Шаблон проектирования Builder

Определение

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

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

Мотивация

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

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

Конструктору передается String, и без определения класса становится очевидным, что он представляет тип / имя 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) { ... } 

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

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

Вот где вступает в силу паттерн Строителя:

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

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

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

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

Выполнение

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

  • static строителя должен быть вложен в наш класс SmartHome
  • Конструктор SmartHome должен быть private чтобы конечный пользователь не мог его вызвать.
  • Класс построителя должен иметь интуитивно понятное имя, например SmartHomeBuilder
  • Класс SmartHomeBuilder будет иметь те же поля, что и класс SmartHome
  • Поля в SmartHome могут быть final или нет, в зависимости от того, хотите ли вы, чтобы они были неизменными или нет.
  • Класс SmartHomeBuilder будет содержать методы, устанавливающие значения, аналогичные методам установки. Эти методы будут использовать SmartHomeBuilder в качестве возвращаемого типа, назначать переданные значения полям статического класса построителя и следовать соглашению об именах построителя. Они обычно начинаются с with , in , at и т.д. вместо set .
  • Класс статического построителя будет содержать build() который вводит эти значения в SmartHome и возвращает его экземпляр.

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

 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 нет общедоступных конструкторов, и единственный способ создать 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 - QuickStart]{.small}

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

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

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

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

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

Заключение

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

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

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

comments powered by Disqus