Источник
Источник

В этом исчерпывающем руководстве мы изучим все важные шаблоны ООП для написания кода.

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

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

Исходя из этого, «банда четырех» в своей классической книге Design Patterns: Elements of Reusable Object-Oriented Software («Приемы объектно-ориентированного проектирования. Паттерны проектирования») разделила эти шаблоны на порождающие, структурные и поведенческие.

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

Тем не менее знание различных шаблонов пригодится для выявления сценариев, в которых они обнаруживаются. Рекомендую обзавестись книгой Design patterns («Паттерны проектирования») из серии Head First, а также Design Patterns Explained: A New Perspective on Object-Oriented Design («Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию»).

Предпочитаю первую из-за интерактивных дискуссий в формате непринужденной беседы, UML-диаграмм и общения в кубиклах  —  это огромное подспорье для понимания шаблона, его плюсов и минусов, а главное, применимости.
 
В этой книге даны не все шаблоны «банды четырех», поэтому вторая тоже пригодится. Другой отличный способ изучения шаблонов проектирования на Java  —  выявление их в различных проектах, фреймворках, библиотеках. Например, в самом JDK используется несколько шаблонов, их стоит изучить начинающим.

23 шаблона ООП для Java-программистов

Изучим один за другим все основные шаблоны ООП, разделив их на порождающие, структурные и поведенческие.

Порождающие шаблоны проектирования

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

Вот список популярных порождающих шаблонов проектирования в Java:

  • шаблоном «Одиночка» обеспечивается наличие только одного экземпляра класса;
  • «Фабричным методом» подклассами создаются объекты;
  • «Абстрактной фабрикой» предоставляется семейство связанных объектов;
  • «Строителем» пошагово строятся сложные объекты;
  • «Прототипом» объекты клонируются.

С порождающими шаблонами упрощаются переиспользование, сопровождаемость, масштабируемость кода, объекты создаются контролируемо, эффективно  —  для соответствия требованиям приложений.
 
1. «Фабричный метод»
Это очень полезный и узнаваемый шаблон для создания экземпляра объектов. Контроля над процессом создания здесь больше, чем в конструкторе.

Вот полный пример этого шаблона:


interface Currency {
String getSymbol();
}
// Код конкретного класса «Rupee»
class Rupee implements Currency {
@Override
public String getSymbol() {
return "Rs";
}
}

// Код конкретного класса «SGD»
class SGDDollar implements Currency {
@Override
public String getSymbol() {
return "SGD";
}
}

// Код конкретного класса «USDollar»
class USDollar implements Currency {
@Override
public String getSymbol() {
return "USD";
}
}

// Код класса «Factory»
class CurrencyFactory {

public static Currency createCurrency (String country) {
if (country. equalsIgnoreCase ("India")){
return new Rupee();
}else if(country. equalsIgnoreCase ("Singapore")){
return new SGDDollar();
}else if(country. equalsIgnoreCase ("US")){
return new USDollar();
}
throw new IllegalArgumentException("No such currency");
}
}

// Код клиента «Factory»
public class Factory {
public static void main(String args[]) {
String country = args[0];
Currency rupee = CurrencyFactory.createCurrency(country);
System.out.println(rupee.getSymbol());
}
}

Read more: https://javarevisited.blogspot.com/2011/12/factory-design-pattern-java-example.html#ixzz8A5CDviub

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

Вот разница между двумя первыми шаблонами.

3. «Строитель»
Это шаблон для создания экземпляра сложного объекта со множеством параметров. Неписаное правило: если параметров для конструктора больше пяти, создать экземпляр очень трудно.

Большинство параметров необязательные, поэтому нет смысла указывать в них null или false. «Строителем» эта проблема решается за счет чистого и текучего интерфейса: указывается, что нужно, и на основе этих параметров возвращается продукт.

Вот пример кода этого шаблона:


public class BuilderPatternExample {

public static void main(String args[]) {

//Создание объекта с помощью шаблона «Строитель»
Cake whiteCake = new Cake.Builder().sugar(1).butter(0.5). eggs(2).vanila(2).flour(1.5). bakingpowder(0.75).milk(0.5).build();

//Торт готов :)
System.out.println(whiteCake);
}
}

class Cake {

private final double sugar; //чашка
private final double butter; //чашка
private final int eggs;
private final int vanila; //ложка
private final double flour; //чашка
private final double bakingpowder; //ложка
private final double milk; //чашка
private final int cherry;

public static class Builder {

private double sugar; //чашка
private double butter; //чашка
private int eggs;
private int vanila; //ложка
private double flour; //чашка
private double bakingpowder; //ложка
private double milk; //чашка
private int cherry;

//Методы «Строителя» для задания свойства
public Builder sugar(double cup){this.sugar = cup; return this; }
public Builder butter(double cup){this.butter = cup; return this; }
public Builder eggs(int number){this.eggs = number; return this; }
public Builder vanila(int spoon){this.vanila = spoon; return this; }
public Builder flour(double cup){this.flour = cup; return this; }

public Builder bakingpowder(double spoon){this.sugar = spoon; return this; }
public Builder milk(double cup){this.milk = cup; return this; }
public Builder cherry(int number){this.cherry = number; return this; }


//Возвращается полностью готовый объект
public Cake build() {
return new Cake(this);
}
}

//Закрытый конструктор для принудительного создания объекта посредством «Строителя»
private Cake(Builder builder) {
this.sugar = builder.sugar;
this.butter = builder.butter;
this.eggs = builder.eggs;
this.vanila = builder.vanila;
this.flour = builder.flour;
this.bakingpowder = builder.bakingpowder;
this.milk = builder.milk;
this.cherry = builder.cherry;
}

@Override
public String toString() {
return "Cake{" + "sugar=" + sugar + ", butter=" + butter + ", eggs=" + eggs + ", vanila=" + vanila + ", flour=" + flour + ", bakingpowder=" + bakingpowder + ", milk=" + milk + ", cherry=" + cherry + '}';

}

}

Output:
Cake{sugar=0.75, butter=0.5, eggs=2, vanila=2, flour=1.5, bakingpowder=0.0, milk=0.5, cherry=0}

Read more: https://javarevisited.blogspot.com/2012/06/builder-design-pattern-in-java-example.html#ixzz8A5O1WPhP

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

В Java имеется пять способов реализации этого шаблона.

Когда появилось программирование через тестирование, «Одиночка» стал антипаттерном из-за негибкости в предоставлении дублеров тестов при модульном тестировании. Этот шаблон подобен статическим методам: очень трудно имитировать. А это ключевое требование разработки через тестирование.

5. «Прототип»
Насколько известно, этим шаблоном процесс клонирования делегируется фактическим клонируемым объектам.

На схеме ниже у общего интерфейса Image имеется метод clone, но клонирование выполняется в ImageOne, а в ImageTwo ничего не клонируется:

Это были порождающие шаблоны из списка «банды четырех» для решения проблем создания объектов.

Структурные шаблоны проектирования

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

Вот список основных структурных шаблонов проектирования в Java:

  • шаблоном «Адаптер» обеспечивается взаимодействие несовместимых интерфейсов;
  • «Мостом»  —  отделение абстракции от реализации;
  • «Компоновщиком»  —  единообразное обращение с объектами и составными объектами;
  • «Декоратором»  —  динамическое добавление объектам функциональности;
  • «Фасадом»  —  единый интерфейс к сложной подсистеме;
  • «Приспособленцем»  —  экономия памяти путем совместного использования объектов;
  • «Заместителем»  —  контроль доступа к объектам.

Со структурными шаблонами создаются хорошо структурированные, расширяемые и модульные Java-приложения.
 
6. «Декоратор»
Этим шаблоном во время выполнения объекту с помощью компоновки добавляется новая, дополнительная функциональность без изменения интерфейса.

7. «Адаптер»
Этим шаблоном преобразуются несовместимые интерфейсы, и взаимодействие двух сторон становится возможным. Вот пример:

8. «Компоновщик»
Это шаблон для единообразного обращения клиента с отдельными объектами и их коллекциями. «Компоновщик» обнаруживается в древовидной структуре.

В дереве узел  —  это либо конечный узел, либо другое дерево, аналогично в шаблоне «Компоновщик» имеются отдельный объект и составные объекты. Хотя поведение при операции с листом и составным объектом отличается, для клиента они прозрачны.

Например, в дереве организации часть сотрудников  —  руководители, остальные  —  подчиненные. Если в методе directs() возвращаются подчиненные руководителя, от рядового сотрудника вернется пустой список, а от руководителя  —  список подчиненных.

9. «Фасад»
Единственная задача этого шаблона  —  упростить применение интерфейса одного или нескольких классов для облегчения их использования.

10. «Заместитель»
Это шаблон для контроля доступа объекта по разным причинам: безопасности, производительности, кеширования и т. д. «Заместителем» реализуется интерфейс реального объекта, это его суррогат. Запрос перенаправляется в реальный объект для обработки, когда этот объект становится доступным.

11. «Приспособленец»

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

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

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

Активно применяется он и в JDK, например с методами Integer.valueOf() и String.valueOf().

12. «Мост»

Этим шаблоном абстракция отделяется от ее реализации  —  причем они изменяются независимо  —  за счет создания двух отдельных иерархий: абстракции, то есть высокоуровневой функциональности, и реализации, то есть деталей низкоуровневой реализации.

«Мостом» обеспечивается гибкость и расширяемость, новые абстракции и реализации добавляются независимо без изменения имеющегося кода. Шаблон приходится кстати, когда в системе имеется множество факторов вариации.

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

«Мост» применяется и для решения проблемы X в Java-приложении, и для уменьшения сложности решения.

Поведенческие шаблоны проектирования

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

Вот список очень полезных поведенческих шаблонов проектирования в Java:

  • шаблоном «Наблюдатель» упрощаются отношения зависимости «один ко многим»;
  • «Командой» инкапсулируются запросы/операции как объекты;
  • «Стратегией» применяются взаимозаменяемые алгоритмы или поведения;
  • «Шаблонным методом» определяется каркас алгоритма, в подклассах реализуются конкретные шаги;
  • «Итератором» предоставляется стандартный способ прохождения коллекций;
  • «Посредником» централизуется взаимодействие объектов.

Благодаря поведенческим шаблонам совершенствуются общая организация и совместное применение Java-приложений, а эти приложения становятся более масштабируемыми и адаптируемыми к изменяющимся требованиям.
 
13. «Стратегия»
Этим шаблоном инкапсулируются взаимозаменяемые поведения  —  стратегия или алгоритм  —  и для определения поведения, применяемого во время выполнения, используется делегирование. «Стратегия» основана на принципе открытости/закрытости: как писать расширяемый код, не трогая уже имеющегося.

Вот пример:

/**
* Программа на Java для реализации «Стратегии» на Java.
* Со «Стратегией» имеется возможность добавить другую стратегию без
* изменения класса «Context», которым применяется эта «Стратегия». В любой
* момент вводится и новая стратегия сортировки. Похожий пример —
* метод «Collections.sort()», которым принимается «Comparator» или «Comparable»,
* то есть стратегия сравнения объектов на Java.
*
* @author WINDOWS 8
*/

public class Test {

public static void main(String args[]) throws InterruptedException {

// добавляем любую стратегию для выполнения сортировки
int[] var = {1, 2, 3, 4, 5 };
Context ctx = new Context(new BubbleSort());
ctx.arrange(var);

// меняем стратегию без изменения класса «Context»
ctx = new Context(new QuickSort());
ctx.arrange(var);
}

}

interface Strategy {
public void sort(int[] numbers);
}

class BubbleSort implements Strategy {

@Override
public void sort(int[] numbers) {
System.out.println("sorting array using bubble sort strategy");

}

}

class InsertionSort implements Strategy {

@Override
public void sort(int[] numbers) {
System.out.println("sorting array using insertion sort strategy");

}
}

class QuickSort implements Strategy {

@Override
public void sort(int[] numbers) {
System.out.println("sorting array using quick sort strategy");

}
}

class MergeSort implements Strategy {

@Override
public void sort(int[] numbers) {
System.out.println("sorting array using merge sort strategy");

}
}

class Context {
private final Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

public void arrange(int[] input) {
strategy.sort(input);
}
}

Output
sorting array using bubble sort strategy
sorting array using quick sort strategy


Read more: https://www.java67.com/2014/12/strategy-pattern-in-java-with-example.html#ixzz8ANP2DOCa

14. «Состояние»
В этом шаблоне при изменении состояния меняется поведение объекта. Он подходит для решения многих интересных задач, например для реализации конечного или торгового автомата на Java.

15. «Команда»
Это шаблон для отделения инициатора действия от объекта, которым действие выполняется фактически. Этот объект инкапсулируется в командный объект, то есть интерфейс с предопределенным методом.

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

Инициатором лишь вызывается метод в командном объекте, откуда запрос затем делегируется конкретному объекту действия:

16. «Шаблонный метод»
Это шаблон для определения алгоритма, но с переносом шагов в подклассы. «Шаблонным методом» алгоритм инкапсулируется в суперкласс, модификация алгоритма не допускается, но за счет реализации в подклассе некоторых шагов повышается гибкость. То есть в подклассе определяется, как реализовать шаги в алгоритме.

17. «Наблюдатель»
Это шаблон для уведомления множества объектов об изменении состояния другого объекта, называемого субъектом или наблюдаемым: за ним наблюдает множество объектов-наблюдателей.

Вот полный пример реального кода реализации шаблона «Наблюдатель»:


import java.util.ArrayList;

interface Observer {
public void update(float interest);
}

interface Subject {
public void registerObserver(Observer observer);

public void removeObserver(Observer observer);

public void notifyObservers();
}

class Loan implements Subject {
private ArrayList<Observer> observers = new ArrayList<Observer>();
private String type;
private float interest;
private String bank;

public Loan(String type, float interest, String bank) {
this.type = type;
this.interest = interest;
this.bank = bank;
}

public float getInterest() {
return interest;
}

public void setInterest(float interest) {
this.interest = interest;
notifyObservers();
}

public String getBank() {
return this.bank;
}

public String getType() {
return this.type;
}

@Override
public void registerObserver(Observer observer) {
observers.add(observer);

}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);

}

@Override
public void notifyObservers() {
for (Observer ob : observers) {
System.out
.println("Notifying Observers on change in Loan interest rate");
ob.update(this.interest);
}

}

}

class Newspaper implements Observer {
@Override
public void update(float interest) {
System.out.println("Newspaper: Interest Rate updated, new Rate is: "
+ interest);
}
}

class Internet implements Observer {
@Override
public void update(float interest) {
System.out.println("Internet: Interest Rate updated, new Rate is: "
+ interest);
}
}

public class ObserverTest {

public static void main(String args[]) {
// так сохранится информация обо всех займах
Newspaper printMedia = new Newspaper();
Internet onlineMedia = new Internet();

Loan personalLoan = new Loan("Personal Loan", 12.5f,
"Standard Charterd");
personalLoan.registerObserver(printMedia);
personalLoan.registerObserver(onlineMedia);
personalLoan.setInterest(3.5f);

}
}

18. «Итератор»
Это стандартизированный способ прохождения элементов коллекции без раскрытия ее базового представления, последовательный доступ к ним с помощью общего интерфейса с отделением логики итерации от реализации коллекции.

Интерфейсом «Итератора» определяются такие методы, как hasNext() для проверки наличия следующего элемента и next() для его извлечения. Чтобы элементы извлекались эффективно и последовательно, встроенными Java-коллекциями ArrayList и LinkedList реализуется специальный итератор.

За счет отделения задач итеративного обхода от основной функциональности коллекции шаблоном «Итератор» упрощается переиспользуемость кода, прохождение коллекции, сопровождаемость.

То есть «Итератор»  —  это способ перемещения по коллекции или агрегирования объектов без раскрытия внутренних деталей Collection:

19. «Посетитель»
Это шаблон ООП на основе концепции двойной диспетчеризации. Уродливая цепочка кода с ifelse  —  для настройки поведения разных объектов одной иерархии классов  —  здесь не нужна, также соблюдается принцип открытости/закрытости SOLID:

20. «Цепочка обязанностей»

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

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

Гибкость, переиспользуемость, масштабируемость повышаются в шаблоне за счет динамического изменения структуры цепочки или добавления новых обработчиков без модифицирования клиентского кода:

21. «Интерпретатор»

Этим шаблоном облегчается создание языкового интерпретатора. Язык или выражение интерпретируется: определяется его грамматика, интерпретатором оцениваются и выполняются выражения.

Шаблон состоит из классов: грамматических правил и терминальных символов. А вот интерпретатором для получения результатов рекурсивно вычисляются нетерминальные символы.

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

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

22. «Посредник»

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

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

Таким разделением повышается гибкость, поскольку компоненты изменяются/развиваются независимо.

Шаблон «Посредник» приходится кстати в сложных системах, где много объектов: им предотвращается сеть сильно связанных отношений, упрощаются сопровождаемость, общий дизайн, организация взаимодействий объектов.

23. «Хранитель»

Это шаблон для фиксации и восстановления внутреннего состояния объекта за счет инкапсуляции состояния в отдельный объект Memento («Хранитель»), сохраняемый затем объектом Caretaker («Опекун»).

Состояния создаются и восстанавливаются объектом Originator («Создатель») с помощью объектов Memento, при этом отменить действия или откатиться к прошлым состояниям  —  не проблема. Шаблоном обеспечивается инкапсуляция данных, и состояние остается скрытым от внешнего доступа, поэтому целостность объекта сохраняется.

Шаблон приходится кстати в сценариях, где требуются функционал отмены/восстановления или записи истории о состоянии объекта.

Вот как соотносятся все эти шаблоны ООП:

Источник изображения: educative.io

Вот и все  —  это был список основных шаблонов ООП для опытных и старших разработчиков не только Java, но и любых других языков ООП, например Python, C++ и TypeScript.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи javinpaul: 23 Design Patterns 99% of Java Developers Should Learn

Предыдущая статьяJetpack Compose Canvas: 10 практических примеров
Следующая статьяКак интегрировать Kafka со Spring Boot