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

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

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

О том что такое порождающие и структурные паттерны (шаблоны) проектирования мы рассматривали в статьях Паттерны проектирования. Часть 1. Порождающие шаблоны и Паттерны проектирования. Часть 2. Структурные шаблоны

Что такое поведенческие паттерны проектирования?

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

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

Суть поведенческих шаблонов

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

Основная цель

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

Зачем они нужны?

  1. Повышение читаемости кода: Поведенческие шаблоны способствуют более ясному описанию взаимодействия компонентов системы, что делает код более читаемым.
  2. Легкость поддержки: Эти шаблоны делают систему более поддатливой к изменениям, что упрощает поддержку и добавление новых функций.
  3. Снижение дублирования кода: Поведенческие шаблоны позволяют избежать дублирования кода, предлагая общие механизмы взаимодействия.

Цепочка Обязанностей (Chain of Responsibility)

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

Суть паттерна

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

Пример

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

import java.util.Scanner;

interface Approver {
    boolean approveLoan(int amount);
}

class Manager implements Approver {
    private final int MAX_APPROVAL_AMOUNT = 10000;
    private Approver next;

    @Override
    public boolean approveLoan(int amount) {
        if (amount <= MAX_APPROVAL_AMOUNT) {
            System.out.println("Заявка одобрена менеджером.");
            return true;
        } else if (next != null) {
            return next.approveLoan(amount);
        } else {
            System.out.println("Нет подходящего уровня авторизации.");
            return false;
        }
    }

    void setNext(Approver next) {
        this.next = next;
    }
}

class Director implements Approver {
    private final int MAX_APPROVAL_AMOUNT = 50000;
    private Approver next;

    @Override
    public boolean approveLoan(int amount) {
        if (amount <= MAX_APPROVAL_AMOUNT) {
            System.out.println("Заявка одобрена директором.");
            return true;
        } else if (next != null) {
            return next.approveLoan(amount);
        } else {
            System.out.println("Нет подходящего уровня авторизации.");
            return false;
        }
    }

    void setNext(Approver next) {
        this.next = next;
    }
}

class President implements Approver {
    private Approver next;

    @Override
    public boolean approveLoan(int amount) {
        System.out.println("Заявка одобрена президентом.");
        return true;
    }

    void setNext(Approver next) {
        this.next = next;
    }
}

public class Main {
    public static void main(String[] args) {
        // Создаем цепочку обработчиков
        Manager manager = new Manager();
        Director director = new Director();
        President president = new President();

        manager.setNext(director);
        director.setNext(president);

        // Запрашиваем сумму кредита
        Scanner scanner = new Scanner(System.in);
        System.out.print("Введите сумму кредита: ");
        int loanAmount = scanner.nextInt();

        // Передаем запрос по цепочке обработчиков
        if (manager.approveLoan(loanAmount)) {
            System.out.println("Кредит одобрен.");
        } else {
            System.out.println("Кредит не одобрен.");
        }
    }
}

В данном примере менеджер, директор и президент представляют разные уровни авторизации. Заявка на кредит передается от менеджера к директору и, наконец, к президенту, если не была одобрена на более низком уровне.

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

Паттерн «Команда» (Command)

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

Суть паттерна

Основная идея паттерна «Команда» заключается в том, чтобы создать абстракцию команды, которая инкапсулирует всю информацию о запросе, делая его объектом. Этот объект команды имеет метод `execute()`, который вызывает нужное действие у получателя запроса.

Пример

Рассмотрим простой пример с использованием паттерна «Команда». Допустим, у нас есть светильник, который можно включить и выключить, и мы хотим создать механизм, который позволяет управлять этим светильником с помощью команд.

// Интерфейс команды
interface Command {
    void execute();
}

// Конкретная команда для включения светильника
class TurnOnCommand implements Command {
    private Light light;

    TurnOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOn();
    }
}

// Конкретная команда для выключения светильника
class TurnOffCommand implements Command {
    private Light light;

    TurnOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOff();
    }
}

// Получатель команды (светильник)
class Light {
    void turnOn() {
        System.out.println("Светильник включен.");
    }

    void turnOff() {
        System.out.println("Светильник выключен.");
    }
}

// Исполнитель команд
class RemoteControl {
    private Command command;

    void setCommand(Command command) {
        this.command = command;
    }

    void pressButton() {
        command.execute();
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        Light light = new Light();
        Command turnOnCommand = new TurnOnCommand(light);
        Command turnOffCommand = new TurnOffCommand(light);

        RemoteControl remoteControl = new RemoteControl();

        // Включаем светильник
        remoteControl.setCommand(turnOnCommand);
        remoteControl.pressButton();

        // Выключаем светильник
        remoteControl.setCommand(turnOffCommand);
        remoteControl.pressButton();
    }
}

В данном примере `Light` представляет светильник, у которого есть методы `turnOn()` и `turnOff()`. Классы `TurnOnCommand` и `TurnOffCommand` реализуют интерфейс `Command` для конкретных команд включения и выключения. `RemoteControl` является исполнителем команд, управляющим светильником.

Использование паттерна «Команда» позволяет изолировать клиента (класс `RemoteControl`) от получателя (светильник), а также обеспечивает гибкость и расширяемость системы, так как мы можем легко добавлять новые команды без изменения существующего кода.

Паттерн «Итератор» (Iterator)

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

Суть паттерна

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

Пример

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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// Интерфейс итератора
interface MyIterator {
    boolean hasNext();
    T next();
}

// Конкретная реализация итератора для списка строк
class StringListIterator implements MyIterator {
    private List stringList;
    private int position;

    StringListIterator(List stringList) {
        this.stringList = stringList;
        this.position = 0;
    }

    @Override
    public boolean hasNext() {
        return position < stringList.size();
    }

    @Override
    public String next() {
        if (hasNext()) {
            return stringList.get(position++);
        } else {
            return null;
        }
    }
}

// Коллекция строк с методом получения итератора
class StringCollection {
    private List strings;

    StringCollection() {
        this.strings = new ArrayList<>();
    }

    void addString(String str) {
        strings.add(str);
    }

    // Метод для получения итератора
    MyIterator iterator() {
        return new StringListIterator(strings);
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        StringCollection stringCollection = new StringCollection();
        stringCollection.addString("One");
        stringCollection.addString("Two");
        stringCollection.addString("Three");

        // Получаем итератор и перебираем элементы коллекции
        MyIterator iterator = stringCollection.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

В этом примере `StringListIterator` реализует интерфейс `MyIterator<String>` для обхода списка строк. `StringCollection` предоставляет метод `iterator()`, который возвращает итератор для данной коллекции. В клиентском коде мы можем использовать этот итератор для последовательного доступа к элементам коллекции.

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

Паттерн «Посредник» (Mediator)

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

Суть паттерна

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

Пример

Давайте рассмотрим пример использования паттерна «Посредник» для реализации чата, где пользователи могут отправлять сообщения друг другу через посредника.

import java.util.ArrayList;
import java.util.List;

// Интерфейс посредника
interface ChatMediator {
    void sendMessage(User user, String message);
}

// Конкретный посредник
class ConcreteChatMediator implements ChatMediator {
    private List users;

    ConcreteChatMediator() {
        this.users = new ArrayList<>();
    }

    void addUser(User user) {
        users.add(user);
    }

    @Override
    public void sendMessage(User user, String message) {
        for (User u : users) {
            // Исключаем отправителя из получателей
            if (u != user) {
                u.receiveMessage(message);
            }
        }
    }
}

// Коллега (участник чата)
class User {
    private String name;
    private ChatMediator mediator;

    User(String name, ChatMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    void sendMessage(String message) {
        System.out.println(name + " отправляет сообщение: " + message);
        mediator.sendMessage(this, message);
    }

    void receiveMessage(String message) {
        System.out.println(name + " получает сообщение: " + message);
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        ConcreteChatMediator chatMediator = new ConcreteChatMediator();

        User user1 = new User("User1", chatMediator);
        User user2 = new User("User2", chatMediator);
        User user3 = new User("User3", chatMediator);

        chatMediator.addUser(user1);
        chatMediator.addUser(user2);
        chatMediator.addUser(user3);

        user1.sendMessage("Привет, как дела?");
        user2.sendMessage("Привет, все отлично!");
    }
}

В этом примере `ConcreteChatMediator` является посредником, который координирует взаимодействие между пользователями (`User`). Пользователи отправляют сообщения через посредника, который рассылает сообщения всем остальным пользователям.

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

Паттерн «Хранитель» (Memento)

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

Суть паттерна

Основная идея паттерна «Хранитель» заключается в выделении объекта-хранителя (`Memento`), который сохраняет внутреннее состояние оригинального объекта (`Originator`). Затем объект-хранитель может быть использован для восстановления состояния объекта-оригинатора.

Пример

Давайте создадим пример, где у нас есть редактор текста (`TextEditor`). Мы хотим иметь возможность сохранять состояние текста и восстанавливать его.

// Объект-хранитель
class TextMemento {
    private String text;

    TextMemento(String text) {
        this.text = text;
    }

    String getText() {
        return text;
    }
}

// Объект-оригинатор
class TextEditor {
    private String text;

    void setText(String text) {
        this.text = text;
    }

    // Создание объекта-хранителя с текущим состоянием
    TextMemento save() {
        return new TextMemento(text);
    }

    // Восстановление состояния из объекта-хранителя
    void restore(TextMemento memento) {
        this.text = memento.getText();
    }

    void printText() {
        System.out.println("Текст: " + text);
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();

        // Редактирование текста
        editor.setText("Hello, World!");
        editor.printText();

        // Сохранение текущего состояния
        TextMemento memento = editor.save();

        // Редактирование текста дальше
        editor.setText("New text");
        editor.printText();

        // Восстановление предыдущего состояния
        editor.restore(memento);
        editor.printText();
    }
}

В этом примере `TextMemento` служит для сохранения состояния текста в `TextEditor`. `TextEditor` может сохранять текущее состояние и восстанавливать его из объекта-хранителя. Это позволяет реализовать отмену операций или восстановление текста к предыдущему состоянию.

Использование паттерна особенно полезно, когда необходимо реализовать механизм отката или сохранения состояния объекта для восстановления.

Паттерн «Наблюдатель» (Observer)

Паттерн проектирования «Наблюдатель» относится к категории поведенческих паттернов и предоставляет механизм оповещения одного объекта о изменениях в другом объекте. Этот паттерн позволяет реализовать слабую связанность между объектами, где изменение состояния одного объекта автоматически приводит к обновлению всех зависящих от него объектов.

Суть паттерна

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

Пример

Давайте рассмотрим пример использования паттерна «Наблюдатель» для отслеживания изменений в погоде. Есть объект, представляющий погоду (`WeatherData`), и объекты, которые хотят отслеживать изменения (`CurrentConditionsDisplay`, `StatisticsDisplay`, `ForecastDisplay`).

import java.util.ArrayList;
import java.util.List;

// Интерфейс для наблюдателя
interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// Интерфейс для объекта-субъекта
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// Объект-субъект
class WeatherData implements Subject {
    private List observers;
    private float temperature;
    private float humidity;
    private float pressure;

    WeatherData() {
        this.observers = new ArrayList<>();
    }

    void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    private void measurementsChanged() {
        notifyObservers();
    }

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

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }
}

// Объект-наблюдатель
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    private void display() {
        System.out.println("Текущие условия: " + temperature + "C градусов и " + humidity + "% влажности");
    }
}

// Другие объекты-наблюдатели (например, StatisticsDisplay, ForecastDisplay) могут быть аналогичными CurrentConditionsDisplay.

// Пример использования
public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();
        weatherData.registerObserver(currentConditionsDisplay);

        // Меняем состояние объекта-субъекта, что приведет к уведомлению и обновлению наблюдателя
        weatherData.setMeasurements(25.0f, 65.0f, 1013.0f);
    }
}

В этом примере `WeatherData` является объектом-субъектом, а `CurrentConditionsDisplay` — объектом-наблюдателем. Когда изменяются показатели погоды в `WeatherData`, все зарегистрированные наблюдатели уведомляются и автоматически обновляются.

Паттерн «Посетитель» (Visitor)

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

Суть паттерна

Основная идея паттерна «Посетитель» заключается в выделении операций, которые могут быть выполнены над объектами, в отдельный класс-посетитель (`Visitor`). Этот класс имеет методы для каждого типа объекта, над которым может быть выполнена операция. Каждый класс, объекты которого могут быть посещены, реализует интерфейс или абстрактный класс. Этот интерфейс или класс предоставляет метод `accept(Visitor visitor)`. А метод в свою очередь передает вызов соответствующему методу посетителя.

Пример

Давайте рассмотрим пример, где у нас есть набор элементов геометрии (`Circle`, `Square`, `Triangle`). Предположим, что мы хотим реализовать операцию вычисления площади для каждого элемента. Для этого мы создадим посетителя (`AreaVisitor`), который реализует методы вычисления площади геометрических элементов каждого типа.

// Интерфейс элемента геометрии
interface Shape {
    void accept(AreaVisitor visitor);
}

// Конкретные реализации элементов геометрии
class Circle implements Shape {
    private double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    double getRadius() {
        return radius;
    }

    @Override
    public void accept(AreaVisitor visitor) {
        visitor.visit(this);
    }
}

class Square implements Shape {
    private double side;

    Square(double side) {
        this.side = side;
    }

    double getSide() {
        return side;
    }

    @Override
    public void accept(AreaVisitor visitor) {
        visitor.visit(this);
    }
}

class Triangle implements Shape {
    private double base;
    private double height;

    Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    double getBase() {
        return base;
    }

    double getHeight() {
        return height;
    }

    @Override
    public void accept(AreaVisitor visitor) {
        visitor.visit(this);
    }
}

// Интерфейс посетителя
interface AreaVisitor {
    void visit(Circle circle);
    void visit(Square square);
    void visit(Triangle triangle);
}

// Реализация посетителя
class CalculateAreaVisitor implements AreaVisitor {
    @Override
    public void visit(Circle circle) {
        double area = Math.PI * Math.pow(circle.getRadius(), 2);
        System.out.println("Площадь круга: " + area);
    }

    @Override
    public void visit(Square square) {
        double area = Math.pow(square.getSide(), 2);
        System.out.println("Площадь квадрата: " + area);
    }

    @Override
    public void visit(Triangle triangle) {
        double area = 0.5 * triangle.getBase() * triangle.getHeight();
        System.out.println("Площадь треугольника: " + area);
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        // Создание элементов геометрии
        Circle circle = new Circle(5);
        Square square = new Square(4);
        Triangle triangle = new Triangle(3, 6);

        // Создание посетителя
        AreaVisitor areaVisitor = new CalculateAreaVisitor();

        // Вычисление площади для каждого элемента
        circle.accept(areaVisitor);
        square.accept(areaVisitor);
        triangle.accept(areaVisitor);
    }
}

В этом примере мы создали интерфейс `Shape`, который реализуют классы `Circle`, `Square` и `Triangle`. Для вычисления площади каждого элемента геометрии мы создали посетителя `AreaVisitor`, реализующего интерфейс `AreaVisitor`. Каждый элемент геометрии реализует метод `accept`, который передает вызов соответствующему методу посетителя. Таким образом, мы можем легко добавлять новые операции над элементами геометрии, не изменяя их классы.

Паттерн «Стратегия» (Strategy)

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

Суть паттерна

Основная идея паттерна «Стратегия» заключается в выделении алгоритма из класса и предоставлении клиенту возможности выбора нужного алгоритма на основе его потребностей. Для этого создаются классы-стратегии, реализующие алгоритмы, и контекст, который содержит ссылку на интерфейс стратегии. Клиент работает с контекстом, выбирая тот или иной алгоритм.

Пример

Например, в приложении для сортировки массива, мы хотим предоставить пользователю возможность выбора различных стратегий сортировки. Создадим интерфейс стратегии `SortingStrategy` и несколько конкретных реализаций этого интерфейса, например, `BubbleSort`, `QuickSort` и `MergeSort`. Затем создадим класс контекста `Sorter`. Этот класс будет содержать ссылку на интерфейс стратегии и предоставлять метод для сортировки массива.

// Интерфейс стратегии
interface SortingStrategy {
    void sort(int[] array);
}

// Конкретные реализации стратегии
class BubbleSort implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Реализация сортировки пузырьком
        System.out.println("Сортировка пузырьком");
    }
}

class QuickSort implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Реализация быстрой сортировки
        System.out.println("Быстрая сортировка");
    }
}

class MergeSort implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Реализация сортировки слиянием
        System.out.println("Сортировка слиянием");
    }
}

// Класс контекста
class Sorter {
    private SortingStrategy strategy;

    // Метод для установки стратегии
    void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    // Метод для сортировки массива
    void sortArray(int[] array) {
        strategy.sort(array);
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        int[] arrayToSort = {5, 2, 8, 1, 7};

        // Создание объектов стратегий
        SortingStrategy bubbleSort = new BubbleSort();
        SortingStrategy quickSort = new QuickSort();
        SortingStrategy mergeSort = new MergeSort();

        // Создание объекта контекста
        Sorter sorter = new Sorter();

        // Использование стратегий для сортировки
        sorter.setStrategy(bubbleSort);
        sorter.sortArray(arrayToSort);

        sorter.setStrategy(quickSort);
        sorter.sortArray(arrayToSort);

        sorter.setStrategy(mergeSort);
        sorter.sortArray(arrayToSort);
    }
}

В этом примере мы создали интерфейс `SortingStrategy`, который реализуют классы `BubbleSort`, `QuickSort` и `MergeSort`. Класс `Sorter` представляет контекст, который содержит ссылку на интерфейс стратегии. Клиент (в данном случае, метод `main`) может выбирать стратегию и использовать ее для сортировки массива.

Паттерн «Состояние» (State)

Паттерн проектирования «Состояние» относится к категории поведенческих паттернов и предоставляет способ изменять поведение объекта при изменении его внутреннего состояния. Это достигается выделением состояний в отдельный класс и делегированием ответственности за поведение объекта этим классам.

Суть паттерна

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

Пример

Представим, у нас есть вентилятор, который может находиться в нескольких состояниях. Определим состояния вентилятора: выключен, работает на минимальной скорости, средней скорости и максимальной скорости. Давайте реализуем паттерн «Состояние» для управления состоянием вентилятора.

// Интерфейс состояния
interface FanState {
    void switchState(FanContext context);
}

// Конкретные реализации состояний
class OffState implements FanState {
    @Override
    public void switchState(FanContext context) {
        System.out.println("Switching to Low state");
        context.setState(new LowState());
    }
}

class LowState implements FanState {
    @Override
    public void switchState(FanContext context) {
        System.out.println("Switching to Medium state");
        context.setState(new MediumState());
    }
}

class MediumState implements FanState {
    @Override
    public void switchState(FanContext context) {
        System.out.println("Switching to High state");
        context.setState(new HighState());
    }
}

class HighState implements FanState {
    @Override
    public void switchState(FanContext context) {
        System.out.println("Switching to Off state");
        context.setState(new OffState());
    }
}

// Контекст - вентилятор
class FanContext {
    private FanState currentState;

    public FanContext() {
        // По умолчанию начинаем с выключенного состояния
        this.currentState = new OffState();
    }

    public void switchState() {
        currentState.switchState(this);
    }

    public void setState(FanState state) {
        this.currentState = state;
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        FanContext fan = new FanContext();

        fan.switchState();  // Включаем на минимальной скорости
        fan.switchState();  // Переключаем на среднюю скорость
        fan.switchState();  // Переключаем на максимальную скорость
        fan.switchState();  // Выключаем
    }
}

В этом примере каждое состояние вентилятора инкапсулировано в собственном классе (OffState, LowState, MediumState, HighState). Контекст (FanContext) делегирует свое текущее состояние объекту-состоянию. При вызове метода `switchState()`, контекст переключает свое состояние в соответствии с логикой текущего состояния.

Паттерн «Шаблонный метод» (Template Method)

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

Суть паттерна

1. Абстрактный Класс (Abstract Class). Определяет «шаблон» алгоритма, включающий в себя абстрактные (или неабстрактные) методы, которые должны быть реализованы подклассами.

2. Конкретный Класс (Concrete Class). Реализует конкретные шаги алгоритма, определенные в абстрактном классе. Может переопределить некоторые шаги, но структура алгоритма остается неизменной.

Пример

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

// Абстрактный класс
abstract class DataProcessor {
    // Шаблонный метод, определяющий основные шаги алгоритма
    public void process() {
        fetchData();
        processData();
        saveData();
    }

    // Абстрактные методы, которые подклассы должны реализовать
    protected abstract void fetchData();
    protected abstract void processData();
    protected abstract void saveData();
}

// Конкретный подкласс
class ConcreteDataProcessor extends DataProcessor {
    // Реализация конкретных шагов алгоритма
    @Override
    protected void fetchData() {
        System.out.println("Fetching data from the source");
    }

    @Override
    protected void processData() {
        System.out.println("Processing data");
    }

    @Override
    protected void saveData() {
        System.out.println("Saving processed data");
    }
}

// Пример использования
public class Main {
    public static void main(String[] args) {
        // Создаем экземпляр конкретного подкласса
        DataProcessor processor = new ConcreteDataProcessor();

        // Вызываем шаблонный метод, который выполняет алгоритм
        processor.process();
    }
}

В этом примере `DataProcessor` определяет алгоритм обработки данных, включая этапы `fetchData`, `processData`, и `saveData`. Конкретный подкласс `ConcreteDataProcessor` реализует эти шаги в соответствии с конкретной логикой приложения. При вызове метода `process` из родительского класса, выполняется шаблонный метод, который включает все указанные этапы.

Техноблог
Добавить комментарий