Паттерны проектирования в 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!