Паттерны проектирования в Java: от теории к практике
Паттерны проектирования (Design Patterns) — это проверенные решения типовых задач при разработке программного обеспечения. Для Java-разработчика знание паттернов — не просто преимущество, а необходимость. Они позволяют писать гибкий, поддерживаемый и масштабируемый код. В этой статье мы разберём четыре ключевых паттерна из каталога GoF (Gang of Four) с реальными примерами на Java.
Почему паттерны важны для Java-разработчика?
Java — строго типизированный объектно-ориентированный язык, где паттерны помогают:
- Уменьшить связанность кода — классы зависят от абстракций, а не от конкретных реализаций.
- Повысить переиспользуемость — готовые решения легко адаптируются под новые задачи.
- Упростить командную работу — паттерны — это общий язык для разработчиков.
1. Singleton (Одиночка)
Паттерн Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В Java классическая реализация выглядит так:
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);
}
}
Когда использовать Singleton?
- Управление подключением к базе данных.
- Логгеры и конфигурационные менеджеры.
- Пул потоков (Thread Pool).
Важно: В современных Java-приложениях Singleton часто заменяют внедрением зависимостей (DI-контейнеры, Spring).
2. Factory Method (Фабричный метод)
Factory Method определяет интерфейс для создания объекта, но позволяет подклассам решать, какой класс инстанциировать. Рассмотрим пример создания уведомлений:
// Интерфейс продукта
interface Notification {
void send(String message);
}
// Конкретные продукты
class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
// Фабрика
abstract class NotificationFactory {
public abstract Notification createNotification();
public void notifyUser(String message) {
Notification notification = createNotification();
notification.send(message);
}
}
class EmailNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new EmailNotification();
}
}
class SMSNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new SMSNotification();
}
}
// Использование
public class Main {
public static void main(String[] args) {
NotificationFactory factory = new EmailNotificationFactory();
factory.notifyUser("Hello, User!");
}
}
Преимущества Factory Method:
- Код не зависит от конкретных классов.
- Добавление новых типов уведомлений не требует изменения существующего кода (принцип Open/Closed).
- Упрощает юнит-тестирование через моки.
3. Observer (Наблюдатель)
Паттерн Observer создаёт механизм подписки, позволяющий одним объектам следить за изменениями других. Реализуем простую систему уведомлений о погоде:
import java.util.ArrayList;
import java.util.List;
// Интерфейс наблюдателя
interface Observer {
void update(float temperature);
}
// Интерфейс субъекта
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// Конкретный субъект
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature);
}
}
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
}
// Конкретный наблюдатель
class TemperatureDisplay implements Observer {
private String name;
public TemperatureDisplay(String name) {
this.name = name;
}
@Override
public void update(float temperature) {
System.out.println(name + " показывает: " + temperature + "°C");
}
}
// Использование
public class WeatherApp {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
TemperatureDisplay display1 = new TemperatureDisplay("Дисплей 1");
TemperatureDisplay display2 = new TemperatureDisplay("Дисплей 2");
station.attach(display1);
station.attach(display2);
station.setTemperature(25.5f);
station.setTemperature(27.0f);
}
}
Где применяется Observer в Java?
- Java Swing/AWT — обработчики событий (ActionListener).
- Паттерн Model-View-Controller (MVC).
- Системы подписок в микросервисах (Event Bus).
4. Builder (Строитель)
Builder отделяет конструирование сложного объекта от его представления. Особенно полезен, когда у класса много опциональных параметров. Пример — создание объекта Pizza:
public class Pizza {
private String dough;
private String sauce;
private boolean cheese;
private boolean pepperoni;
private boolean mushrooms;
private Pizza(PizzaBuilder builder) {
this.dough = builder.dough;
this.sauce = builder.sauce;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.mushrooms = builder.mushrooms;
}
// Геттеры (опущены для краткости)
public static class PizzaBuilder {
private String dough;
private String sauce;
private boolean cheese = false;
private boolean pepperoni = false;
private boolean mushrooms = false;
public PizzaBuilder(String dough, String sauce) {
this.dough = dough;
this.sauce = sauce;
}
public PizzaBuilder addCheese() {
this.cheese = true;
return this;
}
public PizzaBuilder addPepperoni() {
this.pepperoni = true;
return this;
}
public PizzaBuilder addMushrooms() {
this.mushrooms = true;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
}
// Использование
public class PizzaShop {
public static void main(String[] args) {
Pizza pizza = new Pizza.PizzaBuilder("Тонкое", "Томатный")
.addChe