Каталог Design Patterns: Singleton, Observer, Factory, Adapter и другие
Другие статьи про Design Patterns:
- Основы Design Patterns (GoF)
- Creational Patterns (Singleton, Factory, Builder, Prototype, Abstract Factory)
- Обзор Design Patterns
Статья представляет полный каталог ключевых Design Patterns с примерами Singleton, Observer, Factory, Adapter, Iterator, Strategy, Decorator и Template Method с практическими реализациями.
Суть вопроса
Design Patterns — это проверенные шаблоны решений для типичных проблем в разработке ПО. Они позволяют создавать переиспользуемые, легко поддерживаемые и расширяемые архитектуры.
Краткое описание
Design Patterns (проектные паттерны) — это описанные решения типичных задач в объектно-ориентированной разработке. Их систематизировала «Gang of Four» (GoF), документируя практики, проверенные временем.
Категории паттернов GoF:
Creational Patterns (Порождающие паттерны)
- Singleton: гарантирует существование только одного экземпляра
- Factory: создание объектов без прямого использования конструктора
- Abstract Factory: создание семейств связанных объектов
- Builder: пошаговое создание сложных объектов
Structural Patterns (Структурные паттерны)
- Adapter: приведение несовместимых интерфейсов в соответствие
- Decorator: динамическое добавление функциональности
- Facade: упрощённый интерфейс для подсистем
- Proxy: представитель для объекта
Behavioral Patterns (Поведенческие паттерны)
- Observer: уведомления об изменении состояния
- Strategy: взаимозаменяемые алгоритмы
- Iterator: последовательный доступ к элементам коллекции
- Template Method: скелет алгоритма, переопределяемые шаги
Ключевые пункты для подготовки
- Singleton: глобальный экземпляр, потокобезопасная реализация
- Observer: отношение Subject-Observer, слабая связанность
- Factory: централизация создания объектов, полиморфизм
- Adapter: приведение интерфейсов, композиция или наследование
- Iterator: унифицированный доступ к контейнерам, hasNext/next
- Strategy: взаимозаменяемые алгоритмы, принцип Open-Closed
- Decorator: расширение функциональности, динамическое применение
- Template Method: скелет алгоритма, hook-методы
- Для сертификаций: важны для архитектуры и проектирования ПО
Основные компоненты
- Singleton: один экземпляр на класс
- Observer: связь 1:n для уведомлений
- Factory: создание объектов без оператора new
- Adapter: приведение интерфейсов
- Iterator: последовательный доступ к коллекциям
- Strategy: взаимозаменяемые алгоритмы
- Decorator: динамическое расширение функциональности
- Template Method: скелет алгоритма с изменяемыми шагами
Практические примеры
1. Паттерн Singleton
// Потокобезопасный Singleton с ленивой инициализацией
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final String connectionUrl;
// Приватный конструктор предотвращает создание извне
private DatabaseConnection() {
this.connectionUrl = "jdbc:mysql://localhost:3306/mydb";
// Имитация установления соединения
System.out.println("Datenbankverbindung hergestellt");
}
// Double-Checked Locking для потокобезопасности
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public void executeQuery(String sql) {
System.out.println("Führe aus: " + sql);
}
public String getConnectionUrl() {
return connectionUrl;
}
}
// Использование
public class SingletonDemo {
public static void main(String[] args) {
DatabaseConnection conn1 = DatabaseConnection.getInstance();
DatabaseConnection conn2 = DatabaseConnection.getInstance();
System.out.println("Gleiches Objekt: " + (conn1 == conn2)); // true
System.out.println("Connection URL: " + conn1.getConnectionUrl());
conn1.executeQuery("SELECT * FROM users");
}
}
2. Паттерн Observer
import java.util.*;
// Subject (Observable)
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
measurementsChanged();
}
public void measurementsChanged() {
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity);
}
}
}
// Observer
interface Observer {
void update(float temperature, float humidity);
}
// Concrete Observer
class TemperatureDisplay implements Observer {
private float temperature;
@Override
public void update(float temperature, float humidity) {
this.temperature = temperature;
display();
}
public void display() {
System.out.println("Temperaturanzeige: " + temperature + "°C");
}
}
class HumidityDisplay implements Observer {
private float humidity;
@Override
public void update(float temperature, float humidity) {
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Feuchtigkeitsanzeige: " + humidity + "%");
}
}
// Использование
public class ObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
TemperatureDisplay tempDisplay = new TemperatureDisplay();
HumidityDisplay humidityDisplay = new HumidityDisplay();
// Регистрируем наблюдателей
weatherStation.registerObserver(tempDisplay);
weatherStation.registerObserver(humidityDisplay);
// Изменяем измерения
weatherStation.setMeasurements(25.5f, 65.0f);
weatherStation.setMeasurements(26.0f, 63.5f);
// Удаляем наблюдателя
weatherStation.removeObserver(humidityDisplay);
weatherStation.setMeasurements(24.8f, 67.2f);
}
}
3. Паттерн Factory
// Интерфейс Product
interface Shape {
void draw();
}
// Конкретные Products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Kreis");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Rechteck");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Dreieck");
}
}
// Factory
class ShapeFactory {
public Shape createShape(String shapeType) {
if (shapeType == null || shapeType.isEmpty()) {
return null;
}
switch (shapeType.toUpperCase()) {
case "CIRCLE":
return new Circle();
case "RECTANGLE":
return new Rectangle();
case "TRIANGLE":
return new Triangle();
default:
throw new IllegalArgumentException("Unbekannter Shape-Typ: " + shapeType);
}
}
}
// Использование
public class FactoryDemo {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
// Создаём фигуры без оператора new
Shape circle = factory.createShape("CIRCLE");
Shape rectangle = factory.createShape("RECTANGLE");
Shape triangle = factory.createShape("TRIANGLE");
// Используем фигуры
circle.draw();
rectangle.draw();
triangle.draw();
// Factory с конфигурацией
createShapesFromConfig(factory);
}
private static void createShapesFromConfig(ShapeFactory factory) {
String[] shapes = {"CIRCLE", "RECTANGLE", "CIRCLE", "TRIANGLE"};
for (String shapeType : shapes) {
try {
Shape shape = factory.createShape(shapeType);
shape.draw();
} catch (IllegalArgumentException e) {
System.err.println("Fehler: " + e.getMessage());
}
}
}
}
4. Adapter Pattern
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (inkompatible Schnittstelle)
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// Concrete Adaptee
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Spielt VLC-Datei: " + fileName);
}
@Override
public void playMp4(String fileName) {
// Nicht unterstützt
}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// Nicht unterstützt
}
@Override
public void playMp4(String fileName) {
System.out.println("Spielt MP4-Datei: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// Client
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// Eigene Formate unterstützen
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Spielt MP3-Datei: " + fileName);
}
// Andere Formate über Adapter
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Ungültiges Medienformat: " + audioType);
}
}
}
// Verwendung
public class AdapterDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("avi", "video.avi");
}
}
Паттерн Adapter используется, когда нужно интегрировать несовместимые интерфейсы. Давайте рассмотрим пример медиаплеера, который поддерживает MP3, но требует воспроизведения VLC и MP4 файлов с помощью специализированных player’ов.
AudioPlayer поддерживает только MP3. Для других форматов мы создаём MediaAdapter, который преобразует вызовы общего интерфейса в вызовы специальных методов. Адаптер скрывает сложность работы с несовместимыми интерфейсами.
Когда вызывается play("vlc", "movie.vlc"), адаптер создаёт VlcPlayer и вызывает его метод playVlc(). То же происходит с MP4 файлами.
5. Iterator Pattern
import java.util.*;
// Aggregate Interface
interface Container {
Iterator getIterator();
}
// Iterator Interface
interface Iterator {
boolean hasNext();
Object next();
}
// Concrete Aggregate
class NameRepository implements Container {
private String[] names = {"Alice", "Bob", "Charlie", "Diana"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
if (this.hasNext()) {
return names[index++];
}
return null;
}
}
}
// Verwendung
public class IteratorDemo {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for (Iterator iterator = namesRepository.getIterator(); iterator.hasNext();) {
String name = (String) iterator.next();
System.out.println("Name: " + name);
}
// Vergleich mit Java Standard Iterator
List<String> nameList = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
System.out.println("\nMit Java Iterator:");
for (Iterator<String> it = nameList.iterator(); it.hasNext();) {
String name = it.next();
System.out.println("Name: " + name);
}
// Enhanced for loop (syntactic sugar)
System.out.println("\nMit Enhanced for loop:");
for (String name : nameList) {
System.out.println("Name: " + name);
}
}
}
Паттерн Iterator позволяет последовательно перемещаться по элементам коллекции без раскрытия её внутренней структуры. Вместо прямого доступа к массиву или списку мы предоставляем объект, который управляет обходом.
NameRepository содержит приватное поле names с именами. Метод getIterator() возвращает внутренний класс NameIterator, который реализует интерфейс Iterator. Клиент работает только с методами hasNext() и next(), ничего не зная о деталях хранения данных.
Java имеет встроенный Iterator, который работает аналогично. Enhanced for loop - это синтаксический сахар над Iterator API, позволяющий писать более читаемый код.
6. Strategy Pattern
// Strategy Interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String date;
public CreditCardPayment(String name, String cardNumber, String cvv, String date) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.date = date;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € mit Kreditkarte bezahlt.");
System.out.println("Karteninhaber: " + name);
System.out.println("Kartennummer: **** **** **** " + cardNumber.substring(cardNumber.length() - 4));
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € mit PayPal bezahlt.");
System.out.println("PayPal-Account: " + email);
}
}
class BitcoinPayment implements PaymentStrategy {
private String walletAddress;
public BitcoinPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € mit Bitcoin bezahlt.");
System.out.println("Wallet: " + walletAddress.substring(0, 8) + "...");
}
}
// Context
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Keine Zahlungsmethode ausgewählt");
}
paymentStrategy.pay(amount);
}
}
// Verwendung
public class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Verschiedene Zahlungsmethoden
cart.setPaymentStrategy(new CreditCardPayment(
"Max Mustermann", "1234567890123456", "123", "12/25"));
cart.checkout(100);
System.out.println("---");
cart.setPaymentStrategy(new PayPalPayment("max@example.com", "password123"));
cart.checkout(250);
System.out.println("---");
cart.setPaymentStrategy(new BitcoinPayment("1A2B3C4D5E6F7G8H9I0J"));
cart.checkout(500);
// Dynamische Strategieauswahl
dynamicStrategySelection(cart);
}
private static void dynamicStrategySelection(ShoppingCart cart) {
Scanner scanner = new Scanner(System.in);
System.out.println("\nWählen Sie Zahlungsmethode:");
System.out.println("1. Kreditkarte");
System.out.println("2. PayPal");
System.out.println("3. Bitcoin");
int choice = scanner.nextInt();
int amount = 75;
switch (choice) {
case 1:
cart.setPaymentStrategy(new CreditCardPayment(
"Test User", "1111222233334444", "999", "12/24"));
break;
case 2:
cart.setPaymentStrategy(new PayPalPayment("test@example.com", "test123"));
break;
case 3:
cart.setPaymentStrategy(new BitcoinPayment("ABCDEF123456789"));
break;
default:
System.out.println("Ungültige Auswahl");
return;
}
cart.checkout(amount);
scanner.close();
}
}
Паттерн Strategy инкапсулирует различные алгоритмы в отдельных классах, позволяя менять их во время выполнения. Вместо большого количества условных операторов в одном классе мы создаём семейство стратегий, реализующих общий интерфейс.
В нашем примере ShoppingCart не зависит от конкретного способа оплаты. Мы устанавливаем стратегию через setPaymentStrategy() и вызываем checkout(). Корзина делегирует работу текущей стратегии, независимо от её конкретной реализации.
Каждый способ оплаты имеет свою логику: кредитная карта требует данных карты, PayPal работает с электронной почтой, Bitcoin использует адрес кошелька. Благодаря Strategy клиент может добавлять новые методы оплаты без изменения ShoppingCart.
dynamicStrategySelection() демонстрирует выбор стратегии в runtime на основе пользовательского ввода. Это показывает гибкость паттерна при обработке динамических сценариев.
7. Паттерн Decorator
// Интерфейс компонента
interface Coffee {
String getDescription();
double getCost();
}
// Конкретный компонент
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Einfacher Kaffee";
}
@Override
public double getCost() {
return 2.0;
}
}
// Абстрактный декоратор
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Конкретные декораторы
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milch";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Zucker";
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Schlagobers";
}
@Override
public double getCost() {
return super.getCost() + 1.0;
}
}
// Использование
public class DecoratorDemo {
public static void main(String[] args) {
// Простой кофе
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// С молоком
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// С сахаром
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// С взбитыми сливками
coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// Гибкая комбинация
Coffee customCoffee = new SimpleCoffee();
customCoffee = new MilkDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee); // Doppelter Zucker
System.out.println("\nCustom Kaffee:");
System.out.println(customCoffee.getDescription() + " kostet " + customCoffee.getCost() + "€");
}
}
8. Паттерн Template Method
// Абстрактный класс с Template Method
abstract class DataProcessor {
// Template Method (final, чтобы подклассы не переопределяли)
public final void processData() {
loadData();
if (validateData()) {
transformData();
saveData();
} else {
handleError();
}
cleanup();
}
// Абстрактные методы (должны быть реализованы в подклассах)
protected abstract void loadData();
protected abstract void transformData();
protected abstract void saveData();
// Hook Methods (могут быть переопределены)
protected boolean validateData() {
return true; // Стандартная реализация
}
protected void handleError() {
System.out.println("Datenvalidierung fehlgeschlagen!");
}
protected void cleanup() {
System.out.println("Aufräumen abgeschlossen");
}
}
// Конкретная реализация
class CSVDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Lade CSV-Daten...");
data = "name,age,city\nAlice,25,Berlin\nBob,30,Hamburg";
}
@Override
protected void transformData() {
System.out.println("Transformiere CSV-Daten...");
// Конвертирование CSV в JSON
data = data.replace("\n", ",").replace("name", "\"name\"")
.replace("age", "\"age\"").replace("city", "\"city\"");
}
@Override
protected void saveData() {
System.out.println("Speichere transformierte Daten...");
System.out.println("Daten: " + data);
}
@Override
protected boolean validateData() {
// CSV-специфичная валидация
return data != null && data.contains("name") && data.contains("age");
}
}
class XMLDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Lade XML-Daten...");
data = "<users><user name='Alice' age='25'/><user name='Bob' age='30'/></users>";
}
@Override
protected void transformData() {
System.out.println("Transformiere XML-Daten...");
// Конвертирование XML в JSON
data = "[{\"name\":\"Alice\",\"age\":25},{\"name\":\"Bob\",\"age\":30}]";
}
@Override
protected void saveData() {
System.out.println("Speichere transformierte Daten...");
System.out.println("Daten: " + data);
}
@Override
protected void handleError() {
System.out.println("XML-Verarbeitungsfehler!");
// Специальная обработка ошибок для XML
}
}
// Использование
public class TemplateMethodDemo {
public static void main(String[] args) {
System.out.println("=== CSV Prozessor ===");
DataProcessor csvProcessor = new CSVDataProcessor();
csvProcessor.processData();
System.out.println("\n=== XML Prozessor ===");
DataProcessor xmlProcessor = new XMLDataProcessor();
xmlProcessor.processData();
// Демонстрация паттерна Template Method
demonstrateTemplateMethod();
}
private static void demonstrateTemplateMethod() {
System.out.println("\n=== Template Method Demonstration ===");
// Последовательность алгоритма всегда одинакова, варьируется только реализация
DataProcessor[] processors = {
new CSVDataProcessor(),
new XMLDataProcessor()
};
for (DataProcessor processor : processors) {
System.out.println("\nVerarbeite mit " + processor.getClass().getSimpleName() + ":");
processor.processData();
}
}
}
Обзор категорий паттернов
Creational Patterns (Порождающие паттерны)
| Паттерн | Назначение | Пример |
|---|---|---|
| Singleton | Только один экземпляр | Database Connection |
| Factory | Централизация создания объектов | Shape Factory |
| Abstract Factory | Семейства объектов | GUI Factory |
| Builder | Пошаговое построение сложных объектов | StringBuilder |
Структурные паттерны
| Паттерн | Назначение | Пример |
|---|---|---|
| Adapter | Адаптация несовместимых интерфейсов | Media Adapter |
| Decorator | Динамическое расширение функциональности | Coffee Decorator |
| Facade | Упрощённый интерфейс | Home Theater |
| Proxy | Заместитель объекта | Virtual Proxy |
Поведенческие паттерны
| Паттерн | Назначение | Пример |
|---|---|---|
| Observer | Уведомление об изменениях | Weather Station |
| Strategy | Взаимозаменяемые алгоритмы | Payment Strategy |
| Iterator | Последовательный обход | Collection Iterator |
| Template Method | Шаблон алгоритма | Data Processor |
Преимущества Design Patterns
Переиспользуемость
- Проверенные решения можно применять в разных проектах
- Снижается трудозатраты на разработку
- Обеспечивается единообразие архитектуры
Поддерживаемость
- Чёткое распределение ответственности
- Слабая связанность компонентов
- Упрощенные модификация и расширение
Коммуникация
- Единый язык общения между разработчиками
- Задокументированные подходы к решениям
- Быстрое понимание кода при работе в команде
Качество
- Протестированные и надёжные решения
- Меньше багов и проблем
- Повышенное качество кода
Частые экзаменационные вопросы
-
Когда применяется паттерн Singleton? Когда нужна единственная глобальная экземпляр класса (подключение к БД, логгер).
-
В чём отличие между Adapter и Decorator? Adapter приводит несовместимые интерфейсы в соответствие, а Decorator добавляет новую функциональность.
-
Объясните паттерн Observer! Subject уведомляет Observer об изменении состояния, обеспечивая слабую связанность компонентов.
-
Почему Template Method полезен? Определяет основной скелет алгоритма, позволяя подклассам реализовывать отдельные шаги.
Основные источники
- https://refactoring.guru/design-patterns
- https://www.oreilly.com/library/view/head-first-design/0596007124
- https://en.wikipedia.org/wiki/Software_design_pattern
Рекомендуемая литература: Design Patterns
Keine Bücher für Kategorie "design-patterns" gefunden.



