Паттерны проектирования в Java с примерами: от А до Я

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

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

Начать курс

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

Паттерны проектирования (design patterns) — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. В Java, как в строго типизированном объектно-ориентированном языке, паттерны особенно эффективны. Они помогают сделать код гибким, переиспользуемым и легко поддерживаемым. В этой статье мы разберём три основные группы паттернов (по классификации GoF): порождающие, структурные и поведенческие — с реальными примерами на Java.

1. Порождающие паттерны (Creational Patterns)

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

1.1. Singleton (Одиночка)

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

public class DatabaseConnection {
    private static volatile DatabaseConnection instance;
    private String connectionString;

    private DatabaseConnection(String connectionString) {
        this.connectionString = connectionString;
    }

    public static DatabaseConnection getInstance(String connectionString) {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection(connectionString);
                }
            }
        }
        return instance;
    }

    public void connect() {
        System.out.println("Connected to: " + connectionString);
    }
}

// Использование
DatabaseConnection conn = DatabaseConnection.getInstance("jdbc:mysql://localhost:3306/mydb");
conn.connect();

1.2. Factory Method (Фабричный метод)

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

interface Transport {
    void deliver();
}

class Truck implements Transport {
    public void deliver() { System.out.println("Доставка грузовиком по земле"); }
}

class Ship implements Transport {
    public void deliver() { System.out.println("Доставка кораблём по морю"); }
}

abstract class Logistics {
    abstract Transport createTransport();
    
    public void planDelivery() {
        Transport t = createTransport();
        t.deliver();
    }
}

class RoadLogistics extends Logistics {
    Transport createTransport() { return new Truck(); }
}

class SeaLogistics extends Logistics {
    Transport createTransport() { return new Ship(); }
}

// Использование
Logistics logistics = new RoadLogistics();
logistics.planDelivery(); // Вывод: Доставка грузовиком по земле

2. Структурные паттерны (Structural Patterns)

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

2.1. Adapter (Адаптер)

Позволяет объектам с несовместимыми интерфейсами работать вместе. Часто используется для интеграции старого кода (legacy) с новым.

// Старый интерфейс
interface OldSystem {
    String getDataInXML();
}

// Новый интерфейс
interface NewSystem {
    String getDataInJSON();
}

// Реализация старой системы
class LegacySystem implements OldSystem {
    public String getDataInXML() { return "<data>Hello</data>"; }
}

// Адаптер
class XMLToJSONAdapter implements NewSystem {
    private OldSystem oldSystem;
    
    public XMLToJSONAdapter(OldSystem oldSystem) {
        this.oldSystem = oldSystem;
    }
    
    public String getDataInJSON() {
        String xml = oldSystem.getDataInXML();
        return "{\"data\": \"" + xml.replaceAll("<.*?>", "") + "\"}";
    }
}

// Использование
NewSystem adapter = new XMLToJSONAdapter(new LegacySystem());
System.out.println(adapter.getDataInJSON()); // {"data": "Hello"}

2.2. Decorator (Декоратор)

Динамически добавляет объекту новые обязанности. Является гибкой альтернативой наследованию. В Java активно используется в потоках ввода-вывода (BufferedReader, FileInputStream).

interface Pizza {
    String getDescription();
    double getCost();
}

class Margherita implements Pizza {
    public String getDescription() { return "Маргарита"; }
    public double getCost() { return 5.0; }
}

abstract class ToppingDecorator implements Pizza {
    protected Pizza pizza;
    public ToppingDecorator(Pizza pizza) { this.pizza = pizza; }
}

class Cheese extends ToppingDecorator {
    public Cheese(Pizza pizza) { super(pizza); }
    public String getDescription() { return pizza.getDescription() + ", сыр"; }
    public double getCost() { return pizza.getCost() + 1.0; }
}

class Pepperoni extends ToppingDecorator {
    public Pepperoni(Pizza pizza) { super(pizza); }
    public String getDescription() { return pizza.getDescription() + ", пепперони"; }
    public double getCost() { return pizza.getCost() + 2.0; }
}

// Использование
Pizza pizza = new Margherita();
pizza = new Cheese(pizza);
pizza = new Pepperoni(pizza);
System.out.println(pizza.getDescription() + " — $" + pizza.getCost());
// Маргарита, сыр, пепперони — $8.0

3. Поведенческие паттерны (Behavioral Patterns)

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

3.1. Observer (Наблюдатель)

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

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

// Наблюдатель
interface Observer {
    void update(String message);
}

// Субъект
class NewsPublisher {
    private List<Observer> observers = new ArrayList<>();
    
    public void subscribe(Observer observer) { observers.add(observer); }
    public void unsubscribe(Observer observer) { observers.remove(observer); }
    
    public void publishNews(String news) {
        for (Observer obs : observers) {
            obs.update(news);
        }
    }
}

// Конкретные наблюдатели
class EmailSubscriber implements Observer {
    private String name;
    public EmailSubscriber(String name) { this.name = name; }
    public void update(String message) {
        System.out.println(name + " получил email: " + message);
    }
}

class SMSSubscriber implements Observer {
    private String phone;
    public SMSSubscriber(String phone) { this.phone = phone; }
    public void update(String message) {
        System.out.println("SMS на " + phone + ": " + message);
    }
}

// Использование
NewsPublisher publisher = new NewsPublisher();
publisher.subscribe(new EmailSubscriber("Иван"));
publisher.subscribe(new SMSSubscriber("+7-999-123-45-67"));
publisher.publishNews("Вышла Java 21!"); 
// Иван получил email: Вышла Java 21!
// SMS на +7-999-123-45-67: Вышла Java 21!

Блоги

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