Паттерны проектирования в Java: полное руководство с примерами
Паттерны проектирования (design patterns) — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. В Java эти шаблоны особенно востребованы благодаря строгой объектно-ориентированной парадигме языка. Использование паттернов позволяет сделать код более гибким, поддерживаемым и масштабируемым.
В данной статье мы разберём три основные группы паттернов проектирования в Java: порождающие, структурные и поведенческие. Каждый паттерн будет сопровождаться практическим примером кода.
Порождающие паттерны (Creational Patterns)
Порождающие паттерны управляют процессом создания объектов, делая систему независимой от способа их конструирования. Рассмотрим два наиболее популярных шаблона: Singleton и Factory Method.
Singleton (Одиночка)
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("Подключение к " + connectionString);
}
}
Использование:
DatabaseConnection db = DatabaseConnection.getInstance("jdbc:mysql://localhost:3306/mydb");
db.connect();
Factory Method (Фабричный метод)
Factory Method определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. Это позволяет делегировать создание экземпляров дочерним классам.
Пример фабричного метода для создания различных типов уведомлений:
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String message) {
System.out.println("Отправка email: " + message);
}
}
class SMSNotification implements Notification {
public void send(String message) {
System.out.println("Отправка SMS: " + message);
}
}
abstract class NotificationFactory {
public abstract Notification createNotification();
public void notifyUser(String message) {
Notification notification = createNotification();
notification.send(message);
}
}
class EmailNotificationFactory extends NotificationFactory {
public Notification createNotification() {
return new EmailNotification();
}
}
class SMSNotificationFactory extends NotificationFactory {
public Notification createNotification() {
return new SMSNotification();
}
}
Использование:
NotificationFactory factory = new EmailNotificationFactory();
factory.notifyUser("Ваш заказ подтверждён!");
Структурные паттерны (Structural Patterns)
Структурные паттерны описывают способы объединения классов и объектов для формирования более крупных структур. Рассмотрим паттерн Adapter (Адаптер).
Adapter (Адаптер)
Adapter позволяет объектам с несовместимыми интерфейсами работать вместе. Он оборачивает один объект, предоставляя нужный интерфейс для клиента.
Пример адаптера для интеграции старой системы логирования с новым интерфейсом:
// Старая система
class OldLogger {
public void logMessage(String msg) {
System.out.println("OLD LOG: " + msg);
}
}
// Новый интерфейс
interface NewLogger {
void log(String level, String message);
}
// Адаптер
class LoggerAdapter implements NewLogger {
private OldLogger oldLogger;
public LoggerAdapter(OldLogger oldLogger) {
this.oldLogger = oldLogger;
}
@Override
public void log(String level, String message) {
String formatted = level + ": " + message;
oldLogger.logMessage(formatted);
}
}
Использование:
OldLogger oldLogger = new OldLogger();
NewLogger logger = new LoggerAdapter(oldLogger);
logger.log("INFO", "Система запущена");
Поведенческие паттерны (Behavioral Patterns)
Поведенческие паттерны определяют алгоритмы и способы взаимодействия между объектами. Рассмотрим паттерны Observer (Наблюдатель) и Strategy (Стратегия).
Observer (Наблюдатель)
Observer создаёт механизм подписки, позволяющий одним объектам (наблюдателям) отслеживать изменения в другом объекте (субъекте). Этот паттерн широко используется в Java (например, в Swing и JavaFX).
Пример системы уведомлений о погоде:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(float temperature);
}
class WeatherStation {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature);
}
}
}
class Display implements Observer {
private String name;
public Display(String name) {
this.name = name;
}
@Override
public void update(float temperature) {
System.out.println(name + " показывает температуру: " + temperature + "°C");
}
}
Использование:
WeatherStation station = new WeatherStation();
Display display1 = new Display("Дисплей 1");
Display display2 = new Display("Дисплей 2");
station.addObserver(display1);
station.addObserver(display2);
station.setTemperature(25.5f);
Strategy (Стратегия)
Strategy позволяет определять семейство алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Это позволяет изменять поведение объекта во время выполнения.
Пример стратегии оплаты в интернет-магазине:
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println("Оплата " + amount + " руб. с карты " + cardNumber);
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println("Оплата " + amount + " руб. через PayPal (" + email + ")");
}
}
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
Использование:
<