Паттерны проектирования в Java: продвинутый уровень с примерами

Онлайн-тренажер Python для начинающих

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

Начать курс

Продвинутые паттерны проектирования в Java: от Bridge до Visitor

Паттерны проектирования — это проверенные решения типовых задач архитектуры программного обеспечения. Если базовые паттерны (Singleton, Factory, Observer) знакомы каждому Java-разработчику, то продвинутые паттерны часто остаются за кадром. В этой статье мы разберём три мощных паттерна: Bridge, Visitor и Memento. Вы узнаете, когда их применять, и получите готовые примеры кода на Java.

1. Bridge (Мост) — отделяем абстракцию от реализации

Паттерн Bridge используется, когда нужно разделить абстракцию и её реализацию, чтобы они могли изменяться независимо. Это особенно полезно, когда у вас есть несколько измерений изменений (например, разные типы устройств и разные пульты управления).

Когда применять: когда вы хотите избежать «взрыва классов» из-за комбинации разных вариантов.

Пример: Управление устройствами

Представьте, что у нас есть пульты (Remote) и устройства (Device). Мы хотим, чтобы один пульт мог работать с разными устройствами (TV, Radio).

// Реализация (Device)
interface Device {
    void turnOn();
    void turnOff();
    void setVolume(int percent);
}

class TV implements Device {
    @Override
    public void turnOn() { System.out.println("TV включён"); }
    @Override
    public void turnOff() { System.out.println("TV выключен"); }
    @Override
    public void setVolume(int percent) { System.out.println("Громкость TV: " + percent + "%"); }
}

class Radio implements Device {
    @Override
    public void turnOn() { System.out.println("Radio включено"); }
    @Override
    public void turnOff() { System.out.println("Radio выключено"); }
    @Override
    public void setVolume(int percent) { System.out.println("Громкость Radio: " + percent + "%"); }
}

// Абстракция (Remote)
abstract class Remote {
    protected Device device;

    public Remote(Device device) {
        this.device = device;
    }

    public abstract void togglePower();
    public abstract void volumeUp();
    public abstract void volumeDown();
}

class BasicRemote extends Remote {
    private boolean isOn = false;
    private int volume = 10;

    public BasicRemote(Device device) {
        super(device);
    }

    @Override
    public void togglePower() {
        if (isOn) { device.turnOff(); isOn = false; }
        else { device.turnOn(); isOn = true; }
    }

    @Override
    public void volumeUp() {
        volume += 5;
        device.setVolume(volume);
    }

    @Override
    public void volumeDown() {
        volume -= 5;
        device.setVolume(volume);
    }
}

// Использование
public class BridgeDemo {
    public static void main(String[] args) {
        Device tv = new TV();
        Remote remote = new BasicRemote(tv);
        remote.togglePower(); // TV включён
        remote.volumeUp();    // Громкость TV: 15%

        Device radio = new Radio();
        remote = new BasicRemote(radio);
        remote.togglePower(); // Radio включено
        remote.volumeUp();    // Громкость Radio: 15%
    }
}

Преимущество: мы можем добавлять новые типы устройств или новые пульты, не меняя существующий код. Паттерн Bridge идеально подходит для кроссплатформенных приложений (например, UI на разных ОС).

2. Visitor (Посетитель) — новые операции без изменения классов

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

Когда применять: когда у вас есть стабильная иерархия классов, но вы часто добавляете новые операции (например, экспорт в разные форматы, генерация отчётов).

Пример: Экспорт фигур в разные форматы

У нас есть фигуры (Circle, Square) и мы хотим экспортировать их в JSON и XML.

// Элементы (Elements)
interface Shape {
    void accept(Visitor visitor);
}

class Circle implements Shape {
    public double radius = 5.0;

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

class Square implements Shape {
    public double side = 4.0;

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

// Посетитель (Visitor)
interface Visitor {
    void visit(Circle circle);
    void visit(Square square);
}

class JsonExportVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        System.out.println("{ \"type\": \"circle\", \"radius\": " + circle.radius + " }");
    }

    @Override
    public void visit(Square square) {
        System.out.println("{ \"type\": \"square\", \"side\": " + square.side + " }");
    }
}

class XmlExportVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        System.out.println("<circle radius=\"" + circle.radius + "\"/>");
    }

    @Override
    public void visit(Square square) {
        System.out.println("<square side=\"" + square.side + "\"/>");
    }
}

// Использование
public class VisitorDemo {
    public static void main(String[] args) {
        Shape[] shapes = {new Circle(), new Square()};

        Visitor json = new JsonExportVisitor();
        Visitor xml = new XmlExportVisitor();

        for (Shape shape : shapes) {
            shape.accept(json);
            shape.accept(xml);
        }
    }
}

Результат: нам не нужно менять классы Circle или Square, чтобы добавить поддержку нового формата. Достаточно создать нового посетителя.

3. Memento (Хранитель) — сохранение и восстановление состояния

Паттерн Memento позволяет сохранять и восстанавливать предыдущее состояние объекта, не нарушая инкапсуляцию. Это основа для реализации Undo/Redo в приложениях.

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

Пример: Текстовый редактор с Undo

// Memento (снимок состояния)
class EditorMemento {
    private final String content;

    public EditorMemento(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

// Originator (создатель снимка)
class Editor {
    private String content = "";

    public void write(String text) {
        content += text;
    }

    public EditorMemento save() {
        return new EditorMemento(content);
    }

    public void restore(EditorMemento memento) {
        content = memento.getContent();
    }

    public void show() {
        System.out.println("Содержимое: " + content);
    }
}

// Caretaker (управляющий историей)
class History {
    private final Stack<EditorMemento> stack = new Stack<>();

    public void push(EditorMemento memento) {
        stack.push(memento);
    }

    public EditorMemento pop                

Блоги

Рекомендаций по книгам