Паттерны проектирования - DmitryGontarenko/usefultricks GitHub Wiki

Creational patterns

Creational patterns (порождающие паттерны) - обеспечивают гибкое создание объектов без внесения в программу лишних зависимостей.*

Builder

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

Допустим, у нас есть класс Car, который содержит в себе три поля - model, color и price. Для того что бы проинициализировать такой объект, обычно используются два способа:

  1. Инициализация с помощью конструктора:
        Car car = new Car("BMW", "Red", 5000);

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

  1. Инициализация с помощью сеттеров:
        Car car = new Car();
        car.setModel("BMW");
        car.setColor("Red");
        car.setPrice(5000);

При передачи параметров через сеттеры может создаваться достаточно массивная структура.

Расссмотри конструирования класса с помощью Builder. Для этого модифицируем наш класс Car следующим образом:

public class Car {
    private final String model;
    private final String color;
    private final int price;

    public static class Builder {
        private final String model;
        private String color;
        private int price;

        // конструктор класса Builder
        public Builder(String model) {
            this.model = model;
        }

        public Builder color(String color) {
            this.color = color;
            return this;
        }

        public Builder price(int price) {
            this.price = price;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }

    // конструктор класса Car
    private Car(Builder builder) {
        this.model = builder.model;
        this.color = builder.color;
        this.price = builder.price;
    }
}

А теперь проинициализируем экземпляр класса Car:

        Car car = new Car.Builder("BMW")
                .color("Red")
                .price(5000)
                .build();

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

Factory Method

Factory Method (фабричный метод) - пораждающий шаблон проектирования, который позволяет делегировать логики создания объектов дочерним классам.
Пример:

public class Application {
    public static void main(String[] args) {
        DeveloperFactory developerFactory = new JavaDeveloperFactory();
        Developer developer = developerFactory.createDeveloper();

        developer.writeCode();
    }
}

interface Developer {
    void writeCode();
}

class JavaDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Java developer write Java code");
    }
}

class PhpDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("PHP developer write PHP code");
    }
}

interface DeveloperFactory {
    Developer createDeveloper();
}

class JavaDeveloperFactory implements DeveloperFactory {
    @Override
    public Developer createDeveloper() {
        return new JavaDeveloper();
    }
}

class PhpDeveloperFactory implements DeveloperFactory {
    @Override
    public Developer createDeveloper() {
        return new PhpDeveloper();
    }
}

Abstract Factory

Abstract Factory (Абстрактная фабрика) - это порождающий паттер проектирования, который позволяет создавать семейства логически связанных объектов.

Рассмотрим этот паттерн на примере формирования команд для проектов.

/**
 * Интерфейсы членов команды
 * Это общие интерфейсы для отдельных продуктов, 
 * состовляющих семейство (в данном случае команды).
 * Т.е. все разработчики получат интерфейс Developer, тестировщики - QAEngineer и т.д. 
 */
interface Developer {
    void writeCode();
}

interface QAEngineer {
    void testCode();
}

interface ProjectManager {
    void manageProject();
}

/**
 * Фабрика команды. Этот интерфейс возвращает определенных членов команды.
 * Это Абстрактная фабрика - общий интерфейс, содержащий
 * методы создания продуктов для определенного семейства (команды).
 */
interface ProjectTeamFactory {
    Developer getDeveloper();
    QAEngineer getQAEngineer();
    ProjectManager getProjectManager();
}

/**
 * Реализация конкретных членов команды
 * для банковского проекта
 */
class JavaDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Java developer writes Java code");
    }
}

class AutoTester implements QAEngineer {
    @Override
    public void testCode() {
        System.out.println("Tester test banking code");
    }
}

class BankingProjectManager implements ProjectManager {
    @Override
    public void manageProject() {
        System.out.println("Banking PM manages project");
    }
}

/**
 * Фабрика конкретной команды для банковского проекта
 */
class BankingTeamFactory implements ProjectTeamFactory {
    @Override
    public Developer getDeveloper() {
        return new JavaDeveloper();
    }

    @Override
    public QAEngineer getQAEngineer() {
        return new AutoTester();
    }

    @Override
    public ProjectManager getProjectManager() {
        return new BankingProjectManager();
    }
}

/**
 * Реализация конкретных членов команды
 * для игрового проекта
 */
class PythonDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Python developer writes python code");
    }
}

class ManualTester implements QAEngineer {
    @Override
    public void testCode() {
        System.out.println("ManualTester tests game code");
    }
}

class GameDevProjectManager implements ProjectManager {
    @Override
    public void manageProject() {
        System.out.println("GameDev PM manages project");
    }
}

/**
 * Фабрика конкретной команды для игрового проекта
 */
class GameDevTeamFactory implements ProjectTeamFactory {
    @Override
    public Developer getDeveloper() {
        return new PythonDeveloper();
    }

    @Override
    public QAEngineer getQAEngineer() {
        return new ManualTester();
    }

    @Override
    public ProjectManager getProjectManager() {
        return new GameDevProjectManager();
    }
}

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

А теперь давайте перейдем к созданию самих проектов:

/**
 * Банковский проект
 */
class MoscowBankProject {
    public static void main(String[] args) {
        // создаем экземпляр конкретной фабрики для команды
        ProjectTeamFactory projectTeamFactory = new BankingTeamFactory();
        Developer developer = projectTeamFactory.getDeveloper();
        QAEngineer qaEngineer = projectTeamFactory.getQAEngineer();
        ProjectManager projectManager = projectTeamFactory.getProjectManager();

        // и наконец приступаем к созданию нашего банковского продукта
        developer.writeCode(); // Java developer writes Java code
        qaEngineer.testCode(); // Tester test banking code
        projectManager.manageProject(); // Banking PM manages project
    }
}

/**
 * Игорой проект (создается по аналогии с бановским)
 */
class TetrisProject {
    public static void main(String[] args) {
        // создаем экземпляр конкретной фабрики для команды
        ProjectTeamFactory projectTeamFactory = new GameDevTeamFactory();
        Developer developer = projectTeamFactory.getDeveloper();
        QAEngineer qaEngineer = projectTeamFactory.getQAEngineer();
        ProjectManager projectManager = projectTeamFactory.getProjectManager();

        // и приступаем к созданию нашего игрового продукта
        developer.writeCode(); // Python developer writes python code
        qaEngineer.testCode(); // ManualTester tests game code
        projectManager.manageProject(); // GameDev PM manages project
    }
}

Prototype

Prototype (Прототип) - пораждающий шаблон проектирования, который позволяет создавать новые объекты через клонирование другого обьекта вместо создания через конструктор.
Паттернт Prototype уже реализован в JDK посредством интерфейса Clonable.

В качестве примера создадим свою реализацию данного шаблона:

interface Copyable<T> {
    T copy();
}

@ToString
class Human implements Copyable {
    String name;
    int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public Object copy() {
        return new Human(name, age);
    }
}

public class PrototypeApplication {
    public static void main(String[] args) {
        Human originHuman = new Human("John", 23);
        Human copyHuman = (Human) originHuman.copy();

        System.out.println(originHuman); // Human(name=John, age=23)
        System.out.println(copyHuman); // Human(name=John, age=23)
    }
}

Важно отметить, что при копировании создается именно НОВЫЙ объект, т.е. объекты будут ссылаться на разную область в памяти и каждый будет иметь свое собственное, уникальное состояние.

Singleton

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

Способ 1. Простой Singleton (ленивая несинхронизированная реализация):

final class Singleton {
    public static int counter = 0;
    private static Singleton instance;

    // приватный конструктор - запрешает внешним
    // классам создание экземпляра с помощью new
    private Singleton() {
        counter++;
    }

    public static Singleton getInstance() {
        // проверяем - если экземпляр Синглтона еще не
        // был создан, то создаем, в противном случае - 
        // возвращаем уже имеющийся.
        if (instance == null) {
            // вызов приватного конструктора
            instance = new Singleton();
        }
        return instance;
    }
}

public class SingletonApplication {
    public static void main(String[] args) {
        Singleton.getInstance();
        Singleton.getInstance();

        System.out.println(Singleton.counter); // 1
    }
}

В результате на выходе мы всегда увидим значение 1, несмотря на то, что метод getInstance был вызван несколько раз и в конструкторе к полю counter всегда прибавляется единица. Это значит, что наш Singleton работает правильно и объект создается только один раз, при первом обращении.

Но такая реализация синглтона не будет работать в многопоточном режиме, т.к. несколько клиентов потока могут одновременно вызвать метод getInstance(), при этом статический объект instance у них все еще будет равен null и оба этих потока одновременно создадут экмезмпляры объекта new Singleton().
Нам нужно синхронизировать создания объекта, для этого рассмотрим еще несколько примеров:

Способ 2. Простой Singleton (не ленивая многопоточная реализация):

final class Singleton {
    public static int counter = 0;
    private static Singleton instance = new Singleton();

    private Singleton() {
        counter++;
    }

    public static Singleton getInstance() {
        return instance;
    }
}

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

Способ 3. Простой Singleton (ленивая многопоточная реализация с модификатором доступа synchronized):

final class Singleton {
    public static int counter = 0;
    private static Singleton instance;

    private Singleton() {
        counter++;
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Теперь данная реализация будет работать в многопотомной среде и иметь ленивую инициализацию, но синхронизация полезна только один раз, при первом обращении к методу getInstance() (когда несколько потоков одновременно пытаются получить доступ к методу), при последующем обращении к этому методу, синхронизация будет просто забирать время, т.к. поле instance будет уже инициализированно.

Способ 4. Double-Checked Locking (ленивая многопоточная реализация с использованием блокировки с двойной проверкой):

final class Singleton {
    public static int counter = 0;
    private static volatile Singleton instance;

    private Singleton() {
        counter++;
    }

    public static Singleton getInstance() {
        // 
        // 
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

Такая реализация решает проблему предыдущего варианта.
В данном случае, какие то из потоков могут успеть зайти в конструкцию if, т.е. для них instance будет равен null, но далее идет проверка с оператором synchronized и только один поток сможет туда зайти.
При такой реализации необходимо использовать ключево слово volatile, нужное для работы с разделяемыми ресурсами при работе вне оператора synchronized.

Способ 5. Initialization on Demand Holder (ленивая многопоточная реализация):

final class Singleton {
    public static int counter = 0;

    private Singleton() {
        counter++;
    }

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

Данная реализация почти идентичная Способу 2. Единственная разница - в реализации через внутренний класс, он позволяет реализовать ленивую инициализацию. Дело в том, что внутренние классы загружаются только тогда, когда мы обращаемся к ним вперые, в отличии от обычных классов, которые загружаются сразу, при запуске программы.

Способ 6. Enum Singleton (не ленивая потокобезопасная реализация):

enum Singleton {
    INSTANCE;

    private Singleton() {
        System.out.println("Singleton created");
    }

    public void someMethod() {
        // some logic
    }
}

/**
 * Клиентский код
 */
public class SingletonApplication {
    public static void main(String[] args) {
        System.out.println(Singleton.INSTANCE);
        System.out.println(Singleton.INSTANCE);
    }
}

// Output: 
//    Singleton created
//    INSTANCE
//    INSTANCE

Данная пример Синглтона реализован через Enum. В клиентском коде мы обратились к нему два раза и как видно по результату, приватный конструктор был вызван только один раз, при первом обращении.

Structural patterns

Structural patterns (структурные паттерны) - обеспечивают различные способы построения связей между объектами.

Adapter

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

Рассмотрим на примере. У нас есть класс RasterGraphics с готовой реализацией:

class RasterGraphics {
    void drawRasterLine() {
        System.out.println("Рисуем линию");
    }
    void drawRasterSquare() {
        System.out.println("Рисуем квадрат");
    }
}

Но по требованию клиента, работа должна осуществлятся через интерфейс VectorGraphics:

interface VectorGraphics {
    void drawLine();
    void drawSquare();
}

В этом случае, наш класс RasterGraphics не подойдет, т.к. он имеет другую сигнатуру методов.

Для решения этой проблемы применим Адаптер (реализация через наследования):

class VectorGraphicsAdapter extends RasterGraphics implements VectorGraphics {
    @Override
    public void drawLine() {
        drawRasterLine();
    }

    @Override
    public void drawSquare() {
        drawRasterSquare();
    }
}

Мы создали Адаптер, который должен реализовать интерфейс VectorGraphics, а также наследовались от текущий реализации класса RasterGraphics. При этом наш класс VectorGraphicsAdapter , как наследник, получил доступ к методам из класса RasterGraphics.
То есть в качестве реализации интерфейса VectorGraphics мы передаем готовые методы из класса RasterGraphics.

Также Адаптер можно реализовать с помощью композиции:

class VestorGrapgicsAdapter2 implements VectorGraphics {
    private RasterGraphics rasterGraphics;

    public VestorGrapgicsAdapter2(RasterGraphics rasterGraphics) {
        this.rasterGraphics = rasterGraphics;
    }

    @Override
    public void drawLine() {
        rasterGraphics.drawRasterLine();
    }

    @Override
    public void drawSquare() {
        rasterGraphics.drawRasterSquare();
    }
}

В этом случае наш Адаптер также должен реализовывать требуемый интерфейс, но мы используем не наследование класса, а передачу класса в качестве параметра конструктору.
Данный способ более предпочтителен, т.к. Java не поддерживает множественное наследование.

Bridge

Bridge (Мост) - структурный шаблон проектирования. Используется что бы разделять абстракцию и реализацию так, что бы они могли изменяться независимо.
Пример из Википедии - Существует множество типов фигур, каждая со своими свойствами и методами. Однако есть что-то, что объединяет все фигуры. Например, каждая фигура должна уметь рисовать себя, масштабироваться и т. п. В то же время рисование графики может отличаться в зависимости от типа ОС, или графической библиотеки. Фигуры должны иметь возможность рисовать себя в различных графических средах, но реализовывать в каждой фигуре все способы рисования или модифицировать фигуру каждый раз при изменении способа рисования непрактично. В этом случае помогает шаблон мост, позволяя создавать новые классы, которые будут реализовывать рисование в различных графических средах. При использовании такого подхода очень легко можно добавлять как новые фигуры, так и способы их рисования.

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

/**
 * Общий интерфейс разработчиков
 */
interface Developer {
    void writeCode();
}

/**
 * Абстрактная программа (проект)
 */
abstract class Program {
    Developer developer;

    public Program(Developer developer) {
        this.developer = developer;
    }

    abstract void developProgram();
}

/**
 * Конкретные разработчики
 */
class JavaDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Java developer writes Java code");
    }
}

class PythonDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Python developer write python code");
    }
}

/**
 * Конкретные проекты
 */
class BankProject extends Program {
    public BankProject(Developer developer) {
        super(developer);
    }

    @Override
    void developProgram() {
        System.out.println("Bank Project development in progress");
        developer.writeCode();
    }
}

class GameProject extends Program {
    public GameProject(Developer developer) {
        super(developer);
    }

    @Override
    void developProgram() {
        System.out.println("Game Project development in progress");
        developer.writeCode();
    }
}

/**
 * Клиентский код (разработка проектов)
 */
public class BridgeApplication {
    public static void main(String[] args) {
        // Java разработчик может разрабатывать Банковский и Игровой проект
        Program bankJavaProject = new BankProject(new JavaDeveloper());
        Program gameJavaProject = new GameProject(new JavaDeveloper());
        bankJavaProject.developProgram(); // Bank Project development in progress<br>Java developer writes Java code
        gameJavaProject.developProgram();

        // В будущем разработку игрового проекта мы можем отдать Python разработчкиу
        Program gamePythonProject = new GameProject(new PythonDeveloper());
        gamePythonProject.developProgram();
    }
}

Теперь мы без труда сможем добавить еще один проект и/или разработчика в существующую логику:

class RubyDevelopment implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Ruby developer write ruby code");
    }
}

class MoscowExchangeProject extends Program {
    public MoscowExchangeProject(Developer developer) {
        super(developer);
    }

    @Override
    void developProgram() {
        System.out.println("Moscow Exchange development in progress");
        developer.writeCode();
    }
}

public class BridgeApplication {
    public static void main(String[] args) {
        // Разработка проекта Московской биржы ruby-разработчиком
        Program moscowExchangeProject = new MoscowExchangeProject(new RubyDevelopment());
        moscowExchangeProject.developProgram();
    }
}

Composite

Composite (компоновщик) - это структурный паттерн проектирования, который позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней, как с единичным объектом.
Паттерн определяет иерархию классов, которые одновременно могут состоять из примитивных (компонентов) и сложных (составных/композитных) объектов.

Рассмотрим на примере:

/**
 * Компонент (Shape) определяет
 * обший интерфейс для всех
 * компонентов дерева
 */
interface Shape {
    void draw();
}

/**
 * Классы фигур - простые компоненты
 * дерева, не имеющие ответвлений
 */
class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Рисую квадрат");
    }
}

class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Рисую треугольник");
    }
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Рисую круг");
    }
}

/**
 * Композит содержит набор дочерних компонентов,
 * но ничего не знает об их типах. Это могут
 * быть как простые компоненты, так и
 * другие композиты
 */
class Composite implements Shape {
    private List<Shape> components = new ArrayList<>();

    void addComponent(Shape component) {
        components.add(component);
    }
    void removeComponent(Shape component) {
        components.remove(component);
    }

    /**
     * Вызываем метод draw() у каждого компонента -
     * как у фигуры, так и у композита - в это случае
     * он попадет в новый композит и пройдется по своему
     * списку компонентов (фигур и композитов)
     */
    @Override
    public void draw() {
        for (Shape component : components) {
            component.draw();
        }
    }
}

А теперь в клиентском коде реализуем древовидную структуру. Для этого создадим несколько композитов и поместим в них компоненты фигуры, а затем эти композиты - поместим внутрь других композитов, тем самым создав древовидную структуру.

public class CompositeApplication {
    public static void main(String[] args) {
        Composite composite1 = new Composite();
        Composite composite2 = new Composite();
        Composite composite3 = new Composite();

        // Поместим наши компоненты (фигуры) в композиты
        composite1.addComponent(new Square());
        composite1.addComponent(new Triangle());
        composite2.addComponent(new Circle());

        // А теперь поместим ранее созданные композиты
        // внутрь других композитов
        composite3.addComponent(composite1);
        composite3.addComponent(composite2);

        composite3.draw();
        // Output:
        //    Рисую квадрат
        //    Рисую треугольник
        //    Рисую круг
    }
}

Decorator

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

Рассмотрим это на примере.
Для компонента Printer, который обеспечивает вывод переданного значения на консоль, добавим декораторы таким образом, что бы это значение всегда выводился в квадратных скобках:

/**
 * Компонент Печать
 */
interface Printer {
    void print();
}

/**
 * Конкретный компонент - реализация пеечати.
 */
class PrinterImpl implements Printer {
    private String value;
    public PrinterImpl(String value) {
        this.value = value;
    }

    @Override
    public void print() {
        System.out.print(value);
    }
}

/**
 * Абстрактный декоратор
 */
abstract class PrinterDecorator implements Printer {
    Printer printer;
    public PrinterDecorator(Printer printer) {
        this.printer = printer;
    }

    public void print() {
        printer.print();
    }
}

/**
 * Конкретный декаратор - добавляет квадратну скобку перед текстом
 */
class LeftBracketDecorator extends PrinterDecorator {
    public LeftBracketDecorator(Printer printer) {
        super(printer);
    }

    @Override
    public void print() {
        System.out.print("[");
        super.print();
    }
}

/**
 * Конкретный декаратор - добавляет квадратну скобку после текста
 */
class RightBracketDecorator extends PrinterDecorator {
    public RightBracketDecorator(Printer printer) {
        super(printer);
    }

    @Override
    public void print() {
        super.print();
        System.out.print("]");
    }
}

/**
 * Клиентский код
 */
public class DecoratorApplication {
    public static void main(String[] args) {
        Printer printer = new LeftBracketDecorator(new RightBracketDecorator(new PrinterImpl("Hello!")));
        printer.print(); // [Hello!]
    }
}

Facade

Facade (Фасад) - структурный шаблон проектирования, позволяющий представлять простой интерфейс, который скрывает более сложную логику.

Рассмотрим данный шаблон на примере работы Компьютера:

/**
 * Группа объектов Power, Memory и HardDriver.
 * Которые вместе должны обеспечивать логику
 * работы компьютера.
 */
class Power {
    public void on() {
        System.out.println("Power ON");
    }
    public void off() {
        System.out.println("Power OFF");
    }
}

class Memory {
    public void load(String data) {
        System.out.println("Load " + data + " in memory");
    }
}

class HardDrive {
    public String read() {
        return "Some data from hard drive";
    }
}

/**
 * Фасад.
 * Скрывает в себе сложную логику
 * взаимодействия между классами
 * (работы компьютера).
 */
class ComputerFacade {
    private Power power = new Power();
    private Memory memory = new Memory();
    private HardDrive hdd = new HardDrive();

    public void start() {
        power.on();
        memory.load(hdd.read());
        power.off();
    }
}

/**
 * Клиентский код.
 * Клиент использует фасад вместо
 * прямой работы с объектами.
 */
public class FacadeApplication {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}

// Output:
//    Power ON
//    Load Some data from hard drive in memory
//    Power OFF

Flyweight

Flyweight (Легковес/Приспособленец) - структурный шаблон проектирования, который уменьшает потребление памяти, благодоря разделению общего состояния, вынесенного в один объект (Flyweight), между множеством объектов. Т.е. кэширует одинаковые данные, используемые в разных объектак, вместо хранения одинаковых данных в каждом объекте.

Хороший пример - Refactoring GURU - Легковес на Java
Рассмотрми данный паттерн на легком примере:

/**
 * Flyweight объект
 */
interface Developer {
    void writeCode();
}

/**
 * Набор конкретных Flyweight
 */
class JavaDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Java developer writes java code");
    }
}

class PythonDeveloper implements Developer {
    @Override
    public void writeCode() {
        System.out.println("Python developer writes python code");
    }
}

/**
 * Фабрика разработчиков
 */
class DeveloperFactory {
    private Map<String, Developer> developers = new HashMap<>();

    public Developer getDeveloperBySpeciality(String speciality) {
        // получаем разработчика из коллекции по ключу специализации
        Developer developer = developers.get(speciality);

        // если такого объекта (разработчика) в коллекции нет, 
        // то создаем его и кладем в коллекцию
        if (developer == null) {
            switch (speciality) {
                case "java":
                    System.out.println("Create Java developer");
                    developer = new JavaDeveloper();
                    break;
                case "python":
                    System.out.println("Create Python developer");
                    developer = new PythonDeveloper();
                    break;
            }
            developers.put(speciality, developer);
        }

        return developer;
    }
}

/**
 * Клиентский код
 */
public class FlyweightApplication {
    public static void main(String[] args) {
        final DeveloperFactory developerFactory = new DeveloperFactory();

        // добавляем в коллекцию объектов разработчиков
        List<Developer> developers = new ArrayList<Developer>() {{
           add(developerFactory.getDeveloperBySpeciality("java"));
           add(developerFactory.getDeveloperBySpeciality("java"));
           add(developerFactory.getDeveloperBySpeciality("java"));
           add(developerFactory.getDeveloperBySpeciality("python"));
           add(developerFactory.getDeveloperBySpeciality("python"));
        }};

        for (Developer developer : developers) {
            developer.writeCode();
        }
    }
}

Теперь, если мы запустим FlyweightApplication, то увидим следующий результат:

Create Java developer
Create Python developer
Java developer writes java code
Java developer writes java code
Java developer writes java code
Python developer writes python code
Python developer writes python code

Как видно, объекты JavaDeveloper и PythonDeveloper создались только один раз, а потом только переиспользовались.

Proxy

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

Рассмотрим на примере.

interface Image {
    void display();
}

class PlaneImage implements Image {
    private String path;

    public PlaneImage(String path) {
        this.path = path;
        load();
    }

    void load() {
        System.out.println("Загрузка файла по пути " + path);
    }

    @Override
    public void display() {
        System.out.println("Просмотр файла по пути " + path);
    }
}

/**
 * Прокси
 */
class ProxyPlaneImage implements Image {
    private String path;
    private PlaneImage planeImage;

    public ProxyPlaneImage(String path) {
        this.path = path;
    }

    @Override
    public void display() {
        if (planeImage == null) {
            planeImage = new PlaneImage(path);
        }
        planeImage.display();
    }
}

/**
 * Клиентский код
 */
public class ProxyApplication {
    public static void main(String[] args) {
        // 1 Вариант без Прокси
        // Загрузка файла будет независимо от того, был вызван метод display() или нет,
        // потому что метод load() вызывается в конструкторе класса PlaneImage
        Image image1 = new PlaneImage("C:/images/planes"); // Загрузка файла по пути C:/images/planes
        // image1.display();

        // 2 Вариант с Прокси
        // В этом случае поля path в будет заполнено, но загрузка не выполнится,
        // пока не будет вызван метод display() классе PlaneImage (имеющий load() в конструкторе)
        Image image2 = new ProxyPlaneImage("C:/images/planes"); // 
        // image2.display();
    }
}

Behavioral patterns

Behavioral patterns (поведенческие паттерны) обеспечивают эффективную коммуникацию между объектами.

Chain of responsibility

Chain of responsibility (цепочка обязанностей) - поведенческий шаблон проектирования, предназначенный для организации в системе уровней ответственности.
Система состоит из нескольких связанных объектов и сообщения в такой системе обрабатываются по схеме «обработай сам либо перешли другому», то есть одни сообщения обрабатываются на том уровне, где они получены, а другие пересылаются объектам иного уровня.
Этот паттерн позволяет передавать запросы последовательно по цепочке обработчиков. Каждый последующий обработчик решает, может ли он обоработать запрос сам или стоит передать запрос дальше по цепи.

Представим, что у нас есть ряд сервисов для уведомления пользователей - SMS, Email и Push. И в зависимости от приоритета передаваемого сообщения, мы хотим, что бы сообщение отправлялось на один или несколько сервисов уведомлений одновременно.
Рассмотрим данный пример:

/**
 * Вспомогательный класс.
 * Содержит перечень приоритетов и их значения.
 */
class Priority {
    public static final int LOW = 1;
    public static final int MEDIUM = 2;
    public static final int HIGH = 3;
}

/**
 * Абстрактный обработчик.
 * Позволяет избавиться от дублирования
 * кода в конкретных обработчиках
 */
abstract class NotifierHandler {
    private int priority;
    public NotifierHandler(int priority) {
        this.priority = priority;
    }

    // Объект, содержащий обработчик, служит в качестве
    // следующего звена цепочки.
    private NotifierHandler nextNotifier;

    public void setNextNotifier(NotifierHandler nextNotifier) {
        this.nextNotifier = nextNotifier;
    }

    public void notifyMessage(String message, int level) {
        if (level >= priority) {
            write(message);
        }
        if (nextNotifier != null) {
            nextNotifier.notifyMessage(message, level);
        }
    }
    // этот метод реализуется конкретным обработчиком
    abstract void write(String message);
}

/**
 * Перечень конкретных обработчиков.
 * Они содержат код обработки запросов и
 * при получении запроса обработик решает,
 * может ли он обработать запрос или передать его
 * следующему объекту.
 */
class SMSNotifier extends NotifierHandler {
    public SMSNotifier(int priority) { super(priority); }

    @Override
    void write(String message) {
        System.out.println("SMS NOTIFICATION: " + message);
    }
}

class EmailNotifier extends NotifierHandler {
    public EmailNotifier(int priority) { super(priority); }

    @Override
    void write(String message) {
        System.out.println("EMAIL NOTIFICATION: " + message);
    }
}

class PushNotifier extends NotifierHandler {
    public PushNotifier(int priority) { super(priority); }

    @Override
    void write(String message) {
        System.out.println("PUSH NOTIFICATION: " + message);
    }
}

/**
 * Клиентский код
 */
public class ChainApplication {
    public static void main(String[] args) {
        // Устанавливаем приоритеты
        // СМС будет отправляться только с приоритетом уведомления >= HIGH
        // Email - с приоритетом >= MEDIUM, а PUSH - с приоритетом >= LOW
        NotifierHandler smsNotifier = new SMSNotifier(Priority.HIGH);
        NotifierHandler emailNotifier = new EmailNotifier(Priority.MEDIUM);
        NotifierHandler pushNotifier = new PushNotifier(Priority.LOW);

        // Устанавливаем следующее "звено цепи" для объекта
        smsNotifier.setNextNotifier(emailNotifier);
        emailNotifier.setNextNotifier(pushNotifier);

        smsNotifier.notifyMessage("Завтра истекает срок действия вашего пароля!", Priority.HIGH);
        smsNotifier.notifyMessage("Сегодня в нашем магазине весенние скидки", Priority.MEDIUM);
        emailNotifier.notifyMessage("Опробуйте наш новый дизайн!", Priority.LOW);
    }
}

Результат работы программы будет следующим:

//        SMS NOTIFICATION: Завтра истекает срок действия вашего пароля!
//        EMAIL NOTIFICATION: Завтра истекает срок действия вашего пароля!
//        PUSH NOTIFICATION: Завтра истекает срок действия вашего пароля!

//        EMAIL NOTIFICATION: Сегодня в нашем магазине весенние скидки
//        PUSH NOTIFICATION: Сегодня в нашем магазине весенние скидки

//        PUSH NOTIFICATION: Опробуйте наш новый дизайн!

SMS с приоритетом HIGH приходит на все каналы уведомлений;
SMS с приоритетом MEDIUM только на email и push канал;
а EMAIL уведомление с приоритетом LOW только на push канал.

Command

Command (Команда) - поведенческий шаблон проектирования, которые отвечает создание такой структуры, в которой класс-отправитль и класс-получатель не зависят друг от друга напрямую.

Рассмотрим пример, где разработчик вызывает различные SQL-запросы:

/**
 * Абстрактная команда.
 * Общий для всех конкретных команд интерфейс.
 * Обычно хранит только один абстрактный метод execute().
 */
interface Command {
    void execute();
}

/**
 * Класс Receiver (получатель команды).
 * Содержит бизнес-логику программы.
 */
class DatabaseQuery {
    public void insert() {
        System.out.println("Inserting record");
    }

    public void update() {
        System.out.println("Updating record");
    }

    public void delete() {
        System.out.println("Deleting record");
    }
}

/**
 * Перечень конкретных реализаций команды.
 * Обычно команда не содержит какую-либо логику,
 * а лишь передает вызов Получателю.
 */
class InsertCommand implements Command {
    private DatabaseQuery databaseQuery;

    public InsertCommand(DatabaseQuery databaseQuery) {
        this.databaseQuery = databaseQuery;
    }

    @Override
    public void execute() {
        databaseQuery.insert();
    }
}

class UpdateCommand implements Command {
    private DatabaseQuery databaseQuery;

    public UpdateCommand(DatabaseQuery databaseQuery) {
        this.databaseQuery = databaseQuery;
    }

    @Override
    public void execute() {
        databaseQuery.update();
    }
}

class DeleteCommand implements Command {
    private DatabaseQuery databaseQuery;

    public DeleteCommand(DatabaseQuery databaseQuery) {
        this.databaseQuery = databaseQuery;
    }

    @Override
    public void execute() {
        databaseQuery.delete();
    }
}

/**
 * Класс Invoker (вызывающий команду/отправитель)
 * Хранит в себе ссылку на на объект команды и
 * работает только с общим интерфейсом.
 */
@AllArgsConstructor
class Developer {
    private Command insert;
    private Command update;
    private Command delete;

    void insertRecord() {
        insert.execute();
    }

    void updateRecord() {
        update.execute();
    }

    void deleteRecord() {
        delete.execute();
    }
}

/**
 * Клиентский код
 */
public class CommandApplication {
    public static void main(String[] args) {
        DatabaseQuery query = new DatabaseQuery();
        Developer developer = new Developer(
                new InsertCommand(query),
                new UpdateCommand(query),
                new DeleteCommand(query)
        );

        developer.insertRecord();
        developer.updateRecord();
        developer.deleteRecord();
    }
}

Interpret

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

Рассмотрим данный паттер на примере.
Задача состоит в том, что бы передать в качестве значения какое-либо выражение с целью его вычислить, т.е. нам понадобиться каким-то образом интерпритировать получаемое значение и вернуть правильный результат:

interface Expression {
    int interpret();
}

class NumberExpression implements Expression {
    int number;

    public NumberExpression(int number) {
        this.number = number;
    }

    @Override
    public int interpret() {
        return number;
    }
}

class MinusExpression implements Expression {
    private Expression left;
    private Expression right;

    public MinusExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}

class PlusExpression implements Expression {
    private Expression left;
    private Expression right;

    public PlusExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}

class Context {
    // описания логики расчета (оценки) заданного выражения
    public Expression evaluate(String s) {
        int pos = s.length() - 1;
        while (pos > 0) {
            if (Character.isDigit(s.charAt(pos))) {
                pos--;
            } else {
                Expression left = evaluate(s.substring(0, pos));
                Expression right = new NumberExpression(
                        Integer.valueOf(s.substring(pos + 1, s.length())));
                char operator = s.charAt(pos);
                switch (operator) {
                    case '-': return new MinusExpression(left, right);
                    case '+': return new PlusExpression(left, right);
                }
            }
        }
        int result = Integer.valueOf(s);
        return new NumberExpression(result);
    }
}

/**
 * Клиентский код
 */
public class InterpreterApplication {
    public static void main(String[] args) {
        Context context = new Context();
        Expression expression = context.evaluate("1+11");

        System.out.println(expression.interpret()); // 12
    }
}

Mediator

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

Рассмотрим данный паттерн на примере реализации чата:

/**
 * Абстрактный Посредник (Медиатор)
 * для обмена информацией с компонентами.
 * В параметрах метода может содержать ссылку на компонент
 */
interface Chat {
    void sendMessage(String message, User user);
}

/**
 * Абстрактный компонент.
 */
interface User {
    void sendMessage(String message);
    void getMessage(String message);
}

/**
 * Перечень конкретных компонентов (пользователей)
 */
class Admin implements User {
    private Chat chat;
    private String name;

    public Admin(Chat chat, String name) {
        this.chat = chat;
        this.name = name;
    }

    @Override
    public void sendMessage(String message) {
        chat.sendMessage(message, this);
    }

    @Override
    public void getMessage(String message) {
        System.out.println("Администратор " + name + " получил сообщение: " + message);
    }
}

class SimpleUser implements User {
    private Chat chat;
    private String name;

    public SimpleUser(Chat chat, String name) {
        this.chat = chat;
        this.name = name;
    }

    @Override
    public void sendMessage(String message) {
        chat.sendMessage(message, this);
    }

    @Override
    public void getMessage(String message) {
        System.out.println("Пользователь " + name + " получил сообщение: " + message);
    }
}

/**
 * Реализация чата. Конкретный посредник (медиатор).
 * Хранит ссылки на участников системы (клиентов)
 */
class TextChat implements Chat {
    User admin;
    List<User> users = new ArrayList<>();

    public void setAdmin(User admin) {
        this.admin = admin;
    }

    public void setUsers(User user) {
        users.add(user);
    }

    @Override
    public void sendMessage(String message, User user) {
        for (User u : users) {
            u.getMessage(message);
        }
        admin.getMessage(message);
    }
}

/**
 * Клиентский кол.
 */
public class MediatorApplication {
    public static void main(String[] args) {
        TextChat chat = new TextChat();

        User admin = new Admin(chat, "ADMIN");
        User user1 = new SimpleUser(chat, "Дмитрий");
        User user2 = new SimpleUser(chat, "Екатерина");

        // добавляем пользователей в чат
        chat.setAdmin(admin);
        chat.setUsers(user1);
        chat.setUsers(user2);

        user1.sendMessage("Всем привет!");
        admin.sendMessage("Админ зашел в чат");
    }
}

Результат работы программы:

Пользователь Дмитрий получил сообщение: Всем привет!
Пользователь Екатерина получил сообщение: Всем привет!
Администратор ADMIN получил сообщение: Всем привет!

Пользователь Дмитрий получил сообщение: Админ зашел в чат
Пользователь Екатерина получил сообщение: Админ зашел в чат
Администратор ADMIN получил сообщение: Админ зашел в чат

Iterator

Iterator (Итератор) - поведенческий шаблон проектирования, который позволяет реализовать универсальный последовательный доступ к какой-либо коллекции.

Рассмотрим на примере:

/**
 * Абстрактный итератор.
 * Определяет методы перебора коллекции.
 */
interface Iterator {
    boolean hasNext();
    Object next();
}

/**
 * Определяет методы для генерации
 * самого итератора.
 */
interface Collection {
    Iterator getIterator();
}

/**
 * Этот класс содержит конкретную
 * коллекцию и реализует интерфейс
 * генерации итератора.
 * Реализованный итератора будет
 * возвращать экземпляр конкретного итератора.
 */
@Data
@AllArgsConstructor
class JavaDeveloper implements Collection {
    private String name;
    private String[] skills;

    @Override
    public Iterator getIterator() {
        return new SkillIterator();
    }

    /**
     * Конкретный итератор. Реализует алгоритм
     * обхода какой-то конкретной коллекции.
     * Этот класс "внутренний", потому что данная
     * итерация будет работать только для коллекции
     * "внешнего" класа JavaDeveloper
     */
    private class SkillIterator implements Iterator {
        int index = 0; // устанавливаем первый элемент коллекции

        // метод hasNext проверяет, доступен ли следущий элемент
        @Override
        public boolean hasNext() {
            if (index < skills.length) {
                return true;
            }
            return false;
        }

        // метод next возвращает элемент по индексу
        // и сдвигаем индекс на следующий элемент
        @Override
        public Object next() {
            return skills[index++];
        }
    }
}

/**
 * Клиентский код
 */
public class IteratorApplication {
    public static void main(String[] args) {
        String[] skills = {"Java", "Hibernate", "Spring"};
        JavaDeveloper javaDeveloper = new JavaDeveloper("Paul", skills);

        Iterator iterator = javaDeveloper.getIterator();

        System.out.println(javaDeveloper.getName() + " developer skills: ");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

// Output:
//    Paul developer skills:
//    Java
//    Hibernate
//    Spring

Memento

Memento (Хранитель) - поведенческий шаблон проектирования, позволяющий делать снимки внутреннего состояния объектов, а затем восстанавливать их.

Рассмотрим данный паттерн на примере создания проекта, сохранения его в репозиторий и дальнейшего восстановления:

/**
 * Так называемый Создатель (Originator).
 * Может производить снимки своего состояния,
 * а также воспроизводить старое состояние.
 */
class Project {
    private String version;
    private Date date;

    public void setVersion(String version) {
        this.version = version;
        this.date = new Date();
    }

    // сохраняем состояние проекта
    public Save save() {
        return new Save(version, date);
    }

    // загружаем состояние проекта
    public void load(Save save) {
        version = save.getVersion();
        date = save.getDate();
    }

    @Override
    public String toString() {
        return "Project: version=" + version + ", date=" + date;
    }
}

/**
 * Класс Снимок (Memento).
 * Содержит состояние Создателя (в данном случае Project).
 */
@Getter
class Save {
    private final String version;
    private final Date date;

    public Save(String version, Date date) {
        this.version = version;
        this.date = date;
    }
}

/**
 * Класс смотритель (Caretaker)
 * Записывает и восстанавливает состояния Создателя.
 * Хранит истории прошлых состояний.
 */
class GitRepository {
    private Save save;

    public Save getSave() {
        return save;
    }

    public void setSave(Save save) {
        this.save = save;
    }
}

/**
 * Клиентский код
 */
public class MementoApplication {
    public static void main(String[] args) throws InterruptedException {
        GitRepository git = new GitRepository();
        Project project = new Project();

        project.setVersion("1.0"); // создаем проект с версией 1.0
        git.setSave(project.save()); // сохраняем состояние проекта
        System.out.println(project);

        Thread.sleep(5000); // таймаут между обновлением версии проекта
        project.setVersion("2.0"); // создаем проект с версией 2.0
        System.out.println(project);

        project.load(git.getSave()); // загружаем предыдущее состояние
        System.out.println(project);
    }
}

// Output:
// Project: version=1.0, date=Fri Apr 24 17:16:12 MSK 2020
// Project: version=2.0, date=Fri Apr 24 17:16:17 MSK 2020
// Project: version=1.0, date=Fri Apr 24 17:16:12 MSK 2020

Observer

Observer (Наблюдатель) - это поведенческий паттерн проектирования, который позволяет объектам оповещать другие объекты об изменениях своего состояния, с помощью механизма подписок.

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

/**
 * Абстрактный наблюдатель (подписчик),
 * определяет метод, с помощью которого
 * конкретный наблюдатель получает оповещение.
 */
interface Observer {
    void handleEvent(List<String> vacancies);
}

/**
 * Абстрактный предмет наблюдения (издатель),
 * определяет методы для добавления, удаления
 * и оповещения конкретных наблюдателей
 */
interface Observed {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}

/**
 * Конкретный подписчик
 */
class Subscriber implements Observer {
    private String name;

    public Subscriber(String name) {
        this.name = name;
    }

    @Override
    public void handleEvent(List<String> vacancies) {
        System.out.println("Dear " + name + ", we have some changes in vacancies: \n" + vacancies);
    }
}

/**
 * Конкретный издатель.
 * Владеет внетрунним состоянием, изменения
 * которого отслеживается подписчиками.
 */
class JobSite implements Observed {
    private List<String> vacancies = new ArrayList<>();
    private List<Observer> subscribers = new ArrayList<>();

    // Добавить вакансию и сообщить подписчикам
    public void addVacancy(String vacancy) {
        vacancies.add(vacancy);
        notifyObserver();
    }


    // Удалить вакансию и сообщить подписчикам
    public void removeVacancy(String vacancy) {
        vacancies.remove(vacancy);
        notifyObserver();
    }

    @Override
    public void addObserver(Observer observer) {
        subscribers.add(observer);
    }

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

    @Override
    public void notifyObserver() {
        for (Observer observer : subscribers) {
            observer.handleEvent(vacancies);
        }
    }
}

/**
 * Клиентский код
 */
public class ObserverApplication {
    public static void main(String[] args) {
        JobSite jobSite = new JobSite();
        jobSite.addVacancy("Java Developer");
        jobSite.addVacancy("Java Senior Developer");

        jobSite.addObserver(new Subscriber("John"));
        jobSite.addObserver(new Subscriber("Paul"));

        jobSite.addVacancy("Java Team Lead");
    }
}

//        Output:
//        Dear John, we have some changes in vacancies:
//        [Java Developer, Java Senior Developer, Java Team Lead]
//
//        Dear Paul, we have some changes in vacancies:
//        [Java Developer, Java Senior Developer, Java Team Lead]

State

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

Рассмотрим данный паттерн на примере переключания радиостанций:

/**
 * Абстрактное состояние (State)
 */
interface Station {
    void play();
}

/**
 * Перечень конкретных состояний.
 * Реализует поведения, связанные с конкретным
 * состоянием контекста.
 */
class RadioSeven implements Station {
    @Override
    public void play() { System.out.println("Играет Радио 7"); }
}

class RadioDFM implements Station {
    @Override
    public void play() { System.out.println("Играет Радио DFM"); }
}

class RadioGalaxy implements Station {
    @Override
    public void play() { System.out.println("Играет Радио Galaxy"); }
}

/**
 * Класс Контекст.
 * Хранит в себе ссылку на объект состояния и
 * и может обращаться к нему, в зависимости
 * от состояния контекста.
 */
class Radio {
    private Station station;

    void setStation(Station station) {
        this.station = station;
    }

    void nextStation() {
        if (station instanceof RadioSeven) {
            setStation(new RadioDFM());
        } else if (station instanceof RadioDFM) {
            setStation(new RadioGalaxy());
        } else if (station instanceof RadioGalaxy) {
            setStation(new RadioSeven());
        }
    }

    void play() {
        station.play();
    }
}

/**
 * Клиентский код
 */
public class StateApplication {
    public static void main(String[] args) {
        Radio radio = new Radio();
        radio.setStation(new RadioGalaxy());

        for (int i = 0; i < 5; i++) {
            radio.play();
            radio.nextStation();
        }
    }
}

//        Output:
//        Играет Радио Galaxy
//        Играет Радио 7
//        Играет Радио DFM
//        Играет Радио Galaxy
//        Играет Радио 7

Как видно из данного примера, при смене состояния - передачи нового объекта в метод setStation(), меняется и поведение - результат вызова метода play().

Strategy

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

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

/**
 * Абстрактная стратегия
 */
interface Station {
    void play();
}

/**
 * Перечень реализаций
 * конкретной стратегии
 */
class RadioSeven implements Station {
    @Override
    public void play() { System.out.println("Играет Радио 7"); }
}

class RadioDFM implements Station {
    @Override
    public void play() { System.out.println("Играет Радио DFM"); }
}

/**
 * Класс контекст.
 * Хранит в себе ссылку на объект
 * конкретной стратегии и работает с
 * ним через общий интерфейс.
 */
class Radio {
    private Station station;

    public void setStation(Station station) {
        this.station = station;
    }

    public void play() {
        station.play();
    }
}

/**
 * Клиентский код
 */
public class StrategyApplication {
    public static void main(String[] args) {
        Radio radio = new Radio();

        radio.setStation(new RadioSeven());
        radio.play();

        radio.setStation(new RadioDFM());
        radio.play();
    }
}

// Output:
//        Играет Радио 7
//        Играет Радио DFM

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

Template method

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

Рассмотрим данный паттер на примере html-старнички:

/**
 * Абстрактный класс определяет
 * шаги алгоритма и содержит шаблонный метод,
 * состоящий из вызовов этих шагов.
 */
abstract class WebsiteTemplate {
    public void showPage() {
        System.out.println("<body>");
        showPageContent();
        System.out.println("</body>");
    }

    public abstract void showPageContent();
}

/**
 * Перечень конкретный классов.
 * Эти классы переопределяют абстрактные
 * шаги шаблонного метода.
 */
class WelcomePage extends WebsiteTemplate {
    @Override
    public void showPageContent() {
        System.out.println("Welcome!");
    }
}

class NewsPage extends WebsiteTemplate {
    @Override
    public void showPageContent() {
        System.out.println("Some News");
    }
}

/**
 * Клиентский код
 */
public class TemplateMethodApplication {
    public static void main(String[] args) {
        WebsiteTemplate websiteWelcome = new WelcomePage();
        WebsiteTemplate websiteNews = new NewsPage();

        websiteWelcome.showPage();
        websiteNews.showPage();
    }
}

// Output:
//    <body>
//    Welcome!
//    </body>

//    <body>
//    Some News
//    </body>

Как видно из данного примера, в подклассах мы переопределяем только шаг заполнения контента, но вывод самих тегов <body> реализован только один раз и остается неизменным.

Visitor

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

Рассмотрим данный шаблон на примере создания проекта двумя разными разработчиками:

/**
 * Абстрактный элемент.
 * Описывает метод "принятия посетителя"
 */
interface ProjectElement {
    void write(Developer developer);
}

/**
 * Перечень конкретных элементов.
 */
class ProjectCore implements ProjectElement {
    @Override
    public void write(Developer developer) {
        developer.create(this);
    }
}

class Database implements ProjectElement {
    @Override
    public void write(Developer developer) {
        developer.create(this);
    }
}

class Test implements ProjectElement {
    @Override
    public void write(Developer developer) {
        developer.create(this);
    }
}

/**
 * Этот класс также реализует "Элемент проекта"
 * Но содержит в себе все остальные элементы в качестве массива.
 * Такой подход похож на паттерн Composite, когда мы работаем
 * со множеством сгруппированных объектов, как с единой стркутурой.
 */
class Project implements ProjectElement {
    private ProjectElement[] projectElements;

    public Project() {
        this.projectElements = new ProjectElement[]{
                new ProjectCore(),
                new Database(),
                new Test()
        };
    }

    @Override
    public void write(Developer developer) {
        for (ProjectElement element : projectElements) {
            element.write(developer);
        }
    }
}

/**
 * Абстрактный "посетитель".
 * Описывает общие методы для всех
 * типов посетителей.
 */
interface Developer {
    void create(ProjectCore project);
    void create(Database database);
    void create(Test test);
}

/**
 * Перечень конкретных "посетителей".
 * Каждый из них реализует свое
 * собственное поведение.
 */
class JavaDeveloper implements Developer {
    @Override
    public void create(ProjectCore project) { System.out.println("Java developer writes java code"); }

    @Override
    public void create(Database database) { System.out.println("Java developer creates Oracle database"); }

    @Override
    public void create(Test test) { System.out.println("Java developer creates integration tests"); }
}

class PythonDeveloper implements Developer {
    @Override
    public void create(ProjectCore project) { System.out.println("Python developer writes python code"); }

    @Override
    public void create(Database database) { System.out.println("Python developer creates MySQL database"); }

    @Override
    public void create(Test test) { System.out.println("Python developer creates unit tests"); }
}

/**
 * Клиентский код
 */
public class VisitorApplication {
    public static void main(String[] args) {
        Project project = new Project();

        project.write(new JavaDeveloper());
        project.write(new PythonDeveloper());
    }
}

//  Output:
//        Java developer writes java code
//        Java developer creates Oracle database
//        Java developer creates integration tests
//
//        Python developer writes python code
//        Python developer creates MySQL database
//        Python developer creates unit tests
⚠️ **GitHub.com Fallback** ⚠️