Главная Веб-разработка Основные паттерны проектирования на Java

Основные паттерны проектирования на Java

от admin

Простое введение в тему для новичков с примерами кода.

Есть популярная фраза: «Зачем изобретать то, что уже изобретено?» Она актуальна не только в быту, но и в профессиональной деятельности. Например, архитектор использует проверенные конструкции, чтобы обеспечить надёжность проектируемого здания. Точно так же разработчики применяют готовые решения для стандартных задач. Эти решения называются паттернами проектирования, и о них мы поговорим в статье.

Мы познакомимся с концепцией паттернов, их классификацией и примерами реализации на языке Java. Поскольку тема сложная, мы будем намеренно упрощать материал и избегать погружения в технические детали, которые встречаются на практике. Считайте это вводным гайдом.

Представьте конвейер по сборке компьютеров: на одной линии собирают материнские платы, на другой — процессоры, на третьей — блоки питания. Каждая линия специализируется на определённом компоненте и производит различные его модификации. Так же работает паттерн «Фабричный метод», где каждая фабрика создаёт свой тип объектов. Например, одна фабрика отвечает за пользовательские интерфейсы, другая — за форматы документов, а третья — за базы данных. Разработчику достаточно указать параметры, и фабрика создаст нужный объект.

Существует семь структурных паттернов:

Допустим, у вас есть европейская вилка и американская розетка. Без переходника их не получится соединить из-за разных стандартов. Такая же ситуация в программировании, когда у компонентов несовместимые интерфейсы. В этом случае на помощь приходит паттерн «Адаптер»: он делает интерфейсы совместимыми и обеспечивает их взаимодействие.

К основным поведенческим паттернам относятся:

Рассмотрим систему светофоров в большом городе. Каждый светофор управляет движением на своём перекрёстке (как отдельный объект системы), а все светофоры координируются через единый центр управления («Посредник»). В случае аварии или пробки система корректирует режим работы всех светофоров («Наблюдатель»), а алгоритмы регулируют время переключения сигналов в зависимости от ситуации на дороге («Стратегия»).

В следующих разделах мы рассмотрим популярные паттерны из каждой категории на простых примерах. Код будет представлен в упрощённом виде — без сложных оптимизаций, которые обычно используются в реальных проектах. Это поможет лучше понять суть паттернов и начать ими пользоваться. Однако для работы с примерами вам понадобится установить Java и настроить среду разработки под свою операционную систему.

Порождающие паттерны проектирования

В крупных проектах чаще всего встречаются три порождающих шаблона: «Фабричный метод», «Строитель» и «Одиночка». Разберём их подробнее.

«Фабрика» (Factory)

Паттерн «Фабрика» (или «Фабричный метод») — это порождающий паттерн проектирования, который создаёт объекты суперкласса без указания их конкретного типа. Принцип работы построен на использовании общего интерфейса, или абстрактного класса, через который создаются разные реализации объектов. Главное преимущество паттерна заключается в устранении жёсткой привязки к конкретным классам, что делает код гибким и легко расширяемым. Паттерн также инкапсулирует детали создания объектов, что упрощает работу с кодом и делает его более поддерживаемым.

Рассмотрим работу паттерна «Фабрика» на примере программы для пиццерии. Мы создадим фабрику с единым интерфейсом, которая будет производить три вида пиццы: «Четыре сыра», «Пепперони» и «Маргариту». Затем при заказе определённого вида пиццы фабрика вернёт нам готовый объект.

public class MainFactory { public static void main(String[] args) { // Создаём объект фабрики, которая будет производить пиццу PizzaFactory pizzaFactory = new PizzaFactory(); try { // Заказываем пиццу «Четыре сыра» у фабрики Pizza pizza = pizzaFactory.createPizza(“Четыре сыра”); // Вызываем метод приготовления пиццы pizza.doPizza(); } catch (IllegalArgumentException e) { // Если пиццы нет в меню, выводим сообщение об ошибке System.out.println(“Ошибка: ” + e.getMessage()); } } } // Фабрика — главный класс, который создаёт объекты пицц class PizzaFactory { // Метод createPizza принимает название пиццы и возвращает нужный объект public Pizza createPizza(String type) { // Проверяем название пиццы и создаём соответствующий объект switch(type.toLowerCase()) { case “Четыре сыра”: return new FourCheesePizza(); case “Пепперони”: return new PepperoniPizza(); case “Маргарита”: return new MargaritaPizza(); default: throw new IllegalArgumentException(“Такой пиццы нет в меню: ” + type); } } } // Базовый абстрактный класс для всех видов пицц abstract class Pizza { public abstract void doPizza(); } // Конкретные классы пицц class FourCheesePizza extends Pizza { @Override public void doPizza() { System.out.println(“Готовим особую пиццу «Четыре сыра» с моцареллой, пармезаном, горгонзолой и чеддером!”); } } class PepperoniPizza extends Pizza { @Override public void doPizza() { System.out.println(“Готовим классическую пиццу «Пепперони» с острыми колбасками!”); } } class MargaritaPizza extends Pizza { @Override public void doPizza() { System.out.println(“Готовим традиционную пиццу «Маргарита» с томатами и базиликом!”); } } // Если вы запустите этот код, фабрика создаст пиццу «Четыре сыра»

«Строитель» (Builder)

Паттерн «Строитель» предлагает пошаговый способ создания сложных объектов. Его главное преимущество — возможность конструировать разные версии объекта через единый процесс. При этом код остаётся чистым, а настройки объекта можно изменять без изменения его базовой структуры.

В качестве примера рассмотрим пошаговую сборку виртуального компьютера с тремя основными компонентами: мощным центральным процессором (CPU), видеокартой (GPU) и оперативной памятью (memory).

public class MainBuilder { public static void main(String[] args) { // Создаём объект строителя для сборки компьютера ComputerBuilder builder = new GamingPCBuilder(); // Создаём объект директора для управления процессом сборки PCDirector director = new PCDirector(builder); director.constructGamingPC(); // Получаем готовый собранный компьютер Computer gamingPC = builder.getPC(); // Выводим характеристики собранного компьютера System.out.println(gamingPC); } } // Создаём класс Computer для хранения характеристик собранного устройства class Computer { private String CPU; // Процессор private String GPU; // Видеокарта private int memory; // Объём оперативной памяти (ГБ) public Computer(String CPU, String GPU, int memory) { this.CPU = CPU; this.GPU = GPU; this.memory = memory; } @Override public String toString() { return “Компьютер: ” + CPU + “, ” + GPU + “, ” + memory + ” ГБ”; } } // Создаём интерфейс «Строитель компьютеров» для пошаговой сборки interface ComputerBuilder { void setCPU(String CPU); void setGPU(String GPU); void setMemory(int memory); Computer getPC(); // Создаём класс «Игровой компьютер» для сборки компьютеров с мощными характеристиками class GamingPCBuilder implements ComputerBuilder { private String CPU; private String GPU; private int memory; @Override public void setCPU(String CPU) { this.CPU = CPU; } @Override public void setGPU(String GPU) { this.GPU = GPU; } @Override public void setMemory(int memory) { this.memory = memory; } @Override public Computer getPC() { return new Computer(CPU, GPU, memory); } } // Создаём класс «Директор» для управления процессом сборки компьютера class PCDirector { private ComputerBuilder builder; public PCDirector(ComputerBuilder builder) { this.builder = builder; } // Определяем метод для сборки игрового компьютера с заданными характеристиками public void constructGamingPC() { builder.setCPU(“Intel Core i9-13900K”); // Устанавливаем мощный процессор builder.setGPU(“NVIDIA GeForce RTX 4090”); // Устанавливаем топовую видеокарту builder.setMemory(32); // Добавляем 32 ГБ оперативной памяти } } // Компьютер: Intel Core i9-13900K, NVIDIA GeForce RTX 4090, 32 ГБ

«Одиночка» (Singleton)

Паттерн «Одиночка» гарантирует существование только одного экземпляра класса в программе и обеспечивает единую точку доступа к нему. Это предотвращает конфликты при работе с общими ресурсами. Например, если бы существовало несколько экземпляров менеджера настроек, они могли бы противоречить друг другу: один установил бы светлую тему, а другой тёмную. Паттерн «Одиночка» помогает избежать подобных проблем.

Допустим, нам нужно создать общее соединение с базой данных для всего приложения. В этом случае все обращения к базе данных будут проходить через один и тот же экземпляр соединения. Такой подход обеспечит целостность данных и исключит конфликты при работе с ресурсом.

public class MainSingleton { public static void main(String[] args) { // Получаем единственный экземпляр соединения с базой данных через метод getInstance() DBConnection db1 = DBConnection.getInstance(); // Выполняем SQL-запрос на создание базы данных db1.executeQuery(db1.hashCode() + “: CREATE DATABASE Users”); // Получаем тот же самый экземпляр соединения с базой данных DBConnection db2 = DBConnection.getInstance(); // Выполняем SQL-запрос для получения данных из базы db2.executeQuery(db2.hashCode() + “: SELECT * FROM Users”); // Проверяем, что оба объекта ссылаются на один и тот же экземпляр System.out.println(db1 == db2); // true } } // Класс «Соединение с базой данных», который реализует паттерн Singleton class DBConnection { private static DBConnection instance; // Единственный экземпляр класса // Приватный конструктор для предотвращения создания объекта извне private DBConnection() { System.out.println(“Singleton создан. Соединение с БД установлено.”); } // Метод для получения единственного экземпляра класса public static synchronized DBConnection getInstance() { if (instance == null) { // Если экземпляр ещё не создан, создаём его instance = new DBConnection(); } return instance; // Возвращаем единственный экземпляр } // Метод для выполнения SQL-запросов public synchronized void executeQuery(String query) { System.out.println(“Запрос: ” + query); } }

После запуска кода мы получим результат true. Это доказывает, что паттерн «Одиночка» работает корректно — оба объекта db1 и db2 действительно ссылаются на один и тот же экземпляр класса. Рассмотрим вывод в консоли:

Singleton создан. Соединение с БД установлено. // Выполняем SQL-запрос через объект db1 (где 1392838282 — хеш-код объекта, который может меняться при каждом запуске программы) Запрос: 1392838282: CREATE DATABASE Users // Выполняем SQL-запрос через тот же объект db2 (хеш-коды совпадают) Запрос: 1392838282: SELECT * FROM Users // Паттерн «Одиночка» сработал правильно true

Структурные паттерны проектирования

В этом разделе мы рассмотрим три ключевых структурных паттерна современной разработки: «Адаптер», «Декоратор» и «Фасад».

«Адаптер» (Adapter)

Паттерн «Адаптер» — это структурный шаблон проектирования, который позволяет преобразовать интерфейс одного класса в другой интерфейс, ожидаемый клиентом. Благодаря этому классы с несовместимыми интерфейсами могут работать вместе без изменения их исходного кода.

Представьте ситуацию: у вас смартфон с разъёмом Type-C, а наушники — с разъёмом 3.5 мм (Jack). Без переходника их соединить не получится. Давайте разберём ситуацию с помощью кода и попробуем совместить интерфейсы.

// Интерфейс для устройств с разъёмом 3,5 мм (Jack) interface HeadphoneJack { void plugIn(); // Метод для подключения устройства с разъёмом 3,5 мм } // Класс наушников, который реализует интерфейс для разъёма Jack class Headphones implements HeadphoneJack { @Override public void plugIn() { // Подключаем наушники через разъём Jack System.out.println(“Наушники с разъёмом Jack подключены!”); } } // Интерфейс для устройств с разъёмом Type-C interface TypeCDevice { void connect(); // Метод для подключения устройства через разъём Type-C } // Класс-адаптер, который позволяет подключать устройства с разъёмом Jack через Type-C class HeadphoneAdapter implements TypeCDevice { private HeadphoneJack headphoneJack; // Объект с разъёмом Jack, который мы будем подключать через адаптер // Конструктор адаптера принимает устройство с разъёмом Jack public HeadphoneAdapter(HeadphoneJack headphoneJack) { this.headphoneJack = headphoneJack; // Сохраняем переданное устройство } @Override public void connect() { // Подключаем наушники через разъём Jack, несмотря на то, что используем Type-C headphoneJack.plugIn(); } } // Главный класс программы, точка входа public class MainAdapter { public static void main(String[] args) { // Создаём объект наушников с разъёмом 3,5 мм HeadphoneJack headphones = new Headphones(); // Создаём адаптер для подключения наушников через разъём Type-C TypeCDevice adapter = new HeadphoneAdapter(headphones); // Подключаем наушники через адаптер (разъём Type-C) adapter.connect(); } } // Результат: наушники с разъёмом Jack подключены!

«Декоратор» (Decorator)

Паттерн «Декоратор» позволяет расширять функциональность объекта без изменения его исходного кода. Представьте себе подарок: вы можете обернуть его в праздничную бумагу, добавить ленту, прикрепить открытку — и всё это, не трогая сам подарок. В программировании такой подход намного практичнее создания множества отдельных классов для каждой возможной комбинации функций. Вместо этого вы просто «оборачиваете» базовый объект в те дополнительные функции, которые нужны в данный момент.

Представим, что вы каждое утро заказываете кофе по своему вкусу. Сегодня это может быть классический чёрный кофе, завтра — кофе с молоком без сахара, а послезавтра — с сахаром. Давайте реализуем подобный механизм.

Читать также:
Microsoft представила ряд ИИ‑инструментов для бизнес-пользователей

public class MainDecorator { public static void main(String[] args) { // Создаём базовый объект — простой чёрный кофе без добавок Coffee coffee = new BlackCoffee(); // Выводим описание простого чёрного кофе System.out.println(coffee.getDescription()); // Добавляем молоко с помощью декоратора coffee = new MilkDecorator(coffee); // Выводим описание кофе с добавлением молока System.out.println(coffee.getDescription()); // Добавляем сахар с помощью декоратора coffee = new SugarDecorator(coffee); // Выводим описание кофе с молоком и сахаром System.out.println(coffee.getDescription()); } } // Интерфейс для кофе определяет метод получения описания напитка interface Coffee { String getDescription(); } // Класс для чёрного кофе — реализация интерфейса Coffee class BlackCoffee implements Coffee { @Override public String getDescription() { return “Чёрный кофе”; // Просто чёрный кофе без добавок } } // Абстрактный класс-декоратор для расширения функциональности объекта Coffee abstract class CoffeeDecorator implements Coffee { protected Coffee coffee; // Ссылаемся на объект Coffee, который будем модифицировать // Конструктор, принимающий базовый объект Coffee public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; } // Возвращаем описание кофе с добавленными характеристиками public String getDescription() { return coffee.getDescription(); } } // Декоратор для добавления молока в кофе class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); // Передаём кофе в конструктор базового класса } @Override public String getDescription() { return coffee.getDescription() + “, молоко”; // Добавляем молоко к описанию } } // Декоратор для добавления сахара в кофе class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); // Передаём кофе в конструктор базового класса } @Override public String getDescription() { return coffee.getDescription() + “, сахар”; // Добавляем сахар к описанию } }

Если мы запустим код, то получим следующий вывод:

Чёрный кофе Чёрный кофе, молоко Чёрный кофе, молоко, сахар

Мы создали объект класса BlackCoffee (базовый чёрный кофе), а затем с помощью декораторов добавили к нему молоко и сахар. Каждый декоратор оборачивает предыдущий объект и добавляет к его описанию новый ингредиент. В результате получилась гибкая структура, к которой можно и дальше добавлять новые декораторы без изменения существующего кода. Например, попробуйте добавить в наш кофе корицу, сливки или сироп.

«Фасад» (Facade)

Паттерн «Фасад» предоставляет единую точку входа для управления сложной системой и позволяет не вникать в её внутреннюю структуру. Это похоже на пульт от домашнего кинотеатра, с помощью которого можно управлять множеством устройств. Давайте попробуем создать такой пульт.

// Класс для управления аудиосистемой class AudioSystem { // Включаем аудиосистему public void on() { System.out.println(“Аудиосистема 7.1 запущена”); } // Выключаем аудиосистему public void off() { System.out.println(“Аудиосистема 7.1 остановлена”); } } // Класс для управления экраном class Screen { // Включаем экран public void on() { System.out.println(“Изображение активировано”); } // Выключаем экран public void off() { System.out.println(“Изображение деактивировано”); } } // Фасад для упрощения работы с системой домашнего кинотеатра class HomeTheaterFacade { private AudioSystem audioSystem; // Объект аудиосистемы private Screen screen; // Объект экрана // Конструктор, который принимает объекты аудиосистемы и экрана public HomeTheaterFacade(AudioSystem audioSystem, Screen screen) { this.audioSystem = audioSystem; this.screen = screen; } // Метод для включения всей системы кинотеатра public void start() { System.out.println(“Включение системы домашнего кинотеатра:”); screen.on(); // Включаем экран audioSystem.on(); // Включаем аудиосистему } // Метод для выключения всей системы кинотеатра public void end() { System.out.println(“Выключение системы домашнего кинотеатра:”); audioSystem.off(); // Выключаем аудиосистему screen.off(); // Выключаем экран } } // Главный класс, где запускаем систему public class MainFacade { public static void main(String[] args) { // Создаём объекты экрана и аудиосистемы Screen screen = new Screen(); AudioSystem audioSystem = new AudioSystem(); // Создаём фасад, чтобы управлять системой кинотеатра через него HomeTheaterFacade homeTheater = new HomeTheaterFacade(audioSystem, screen); // Включаем систему кинотеатра homeTheater.start(); // Выключаем систему кинотеатра homeTheater.end(); } }

После запуска кода мы увидим, как объект фасада (HomeTheaterFacade) управляет всеми устройствами в качестве единой системы:

Включение системы домашнего кинотеатра: Изображение активировано Аудиосистема 7.1 запущена Выключение системы домашнего кинотеатра: Аудиосистема 7.1 остановлена Изображение деактивировано

Поведенческие паттерны проектирования

В этом разделе мы познакомимся с тремя поведенческими шаблонами: «Стратегия», «Наблюдатель» и «Посетитель». Посмотрим, как эти паттерны определяют различные способы взаимодействия объектов в программе.

«Стратегия» (Strategy)

Паттерн «Стратегия» определяет семейство взаимозаменяемых алгоритмов для решения схожей задачи, где каждый алгоритм инкапсулирован в отдельный класс. Например, в навигационном приложении можно реализовать разные способы построения маршрута как отдельные классы — автомобильный, пешеходный и велосипедный. Такой подход делает код чище и значительно упрощает добавление новых способов навигации.

Далее мы рассмотрим паттерн «Стратегия» на примере платёжного терминала с двумя способами оплаты: банковской картой и по QR-коду. Каждый из этих способов будет отдельным взаимозаменяемым алгоритмом.

public class MainStrategy { public static void main(String[] args) { ShoppingCart cart = new ShoppingCart(); // Создаём корзину покупок // Выбираем оплату картой и выполняем платёж cart.setPaymentStrategy(new CreditCardPayment(“1234-5678-9012-3456”)); cart.checkout(150.00); // Меняем способ оплаты на QR-код и снова совершаем платёж cart.setPaymentStrategy(new QRCodePayment(“QR1234567890”)); cart.checkout(75.50); } } // Интерфейс для способов оплаты interface PaymentStrategy { void pay(double amount); // Метод для выполнения платежа } // Оплата банковской картой class CreditCardPayment implements PaymentStrategy { private String cardNumber; // Номер карты public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; } @Override public void pay(double amount) { System.out.println(“Оплачено картой ” + cardNumber + ” на сумму: $” + amount); } } // Оплата по QR-коду class QRCodePayment implements PaymentStrategy { private String qrCode; // Код для оплаты public QRCodePayment(String qrCode) { this.qrCode = qrCode; } @Override public void pay(double amount) { System.out.println(“Оплачено по QR-коду ” + qrCode + ” на сумму: $” + amount); } } // Класс корзины покупок, который использует разные способы оплаты class ShoppingCart { private PaymentStrategy paymentStrategy; // Выбранный способ оплаты // Устанавливаем стратегию оплаты public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } // Выполняем оплату выбранным способом public void checkout(double amount) { if (paymentStrategy == null) { System.out.println(“Ошибка: не выбран способ оплаты!”); return; } paymentStrategy.pay(amount); } }

При запуске программы мы увидим, что она может успешно обрабатывать платежи обоими способами через единый интерфейс PaymentStrategy:

Оплачено картой 1234-5678-9012-3456 на сумму: $150.0 Оплачено по QR-коду QR1234567890 на сумму: $75.5

«Наблюдатель» (Observer)

Паттерн «Наблюдатель» — это способ организации связи между объектами по принципу «один ко многим». Его суть проста: когда основной объект (субъект) меняет своё состояние, все зависимые объекты (наблюдатели) автоматически получают уведомление и обновляются. Наблюдателей можно свободно добавлять или удалять в любой момент, когда это необходимо.

Возьмём в качестве примера систему уведомлений в социальной сети. Когда автор публикует новый пост, видео или фотографию, его подписчики сразу получают об этом уведомление. Теперь посмотрим, как это работает в Java.

import java.util.ArrayList; import java.util.List; // Интерфейс для наблюдателей interface Observer { void update(String message); } // Класс подписчика, который реализует интерфейс Observer class Subscriber implements Observer { @Override public void update(String message) { System.out.println(“Подписчик получил обновление: ” + message); } } // Класс субъекта, который меняет состояние и сообщает подписчикам class Subject { private List<Observer> observers = new ArrayList<>(); private String state; // Метод для добавления подписчика в список public void attach(Observer observer) { observers.add(observer); } // Метод для уведомления всех подписчиков public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } // Метод для изменения состояния public void setState(String state) { this.state = state; notifyObservers(); } } // Создаём объекты и запускаем процесс public class MainObserver { public static void main(String[] args) { Subject subject = new Subject(); Subscriber subscriber = new Subscriber(); subject.attach(subscriber); // Меняем состояние и уведомляем подписчика subject.setState(“Новость: состояние 1”); subject.setState(“Новость: состояние 2”); } }

При вызове метода setState() субъект сразу уведомляет всех зарегистрированных наблюдателей о смене состояния через метод notifyObservers(). В ответ каждый наблюдатель выполняет метод update(), который в нашем примере выводит полученное сообщение в консоль:

Подписчик получил обновление: Новость: состояние 1 Подписчик получил обновление: Новость: состояние 2

«Посетитель» (Visitor)

И последний шаблон на сегодня — «Посетитель». Он позволяет добавлять новую функциональность к классам без изменения их исходного кода. Это особенно ценно в проектах, где требуется регулярно внедрять новые операции в действующую систему, сохраняя при этом её стабильность.

Представьте программу для зоопарка с базой данных различных животных. Для их кормления можно создать класс «Кормильщик», который будет учитывать особенности питания каждого вида животных и не изменять код самих классов животных. Если в будущем нужно добавить функцию лечения, нам достаточно создать новый класс «Ветеринар», и он также не затронет текущий код. Для закрепления накормим домашних питомцев.

public class MainVisitor { public static void main(String[] args) { // Создаём массив домашних животных Animal[] animals = {new Cat(), new Dog()}; // Создаём посетителя, который будет кормить наших питомцев AnimalVisitor feedingVisitor = new FeedingVisitor(); // Для каждого животного вызываем метод accept, который передаёт посетителю текущее животное for (Animal animal : animals) { animal.accept(feedingVisitor); } } } // Интерфейс для животных interface Animal { void accept(AnimalVisitor visitor); // Метод для принятия посетителя } // Интерфейс для посетителей interface AnimalVisitor { void visit(Cat cat); // Метод для кошек void visit(Dog dog); // Метод для собак } // Класс кошки class Cat implements Animal { @Override public void accept(AnimalVisitor visitor) { visitor.visit(this); // Кошка передаёт себя посетителю } public void meow() { System.out.println(“Мяу!”); // Кошка мяукает } } // Класс собаки class Dog implements Animal { @Override public void accept(AnimalVisitor visitor) { visitor.visit(this); // Собака передаёт себя посетителю } public void bark() { System.out.println(“Гав!”); // Собака лает } } // Конкретный посетитель, который будет кормить животных class FeedingVisitor implements AnimalVisitor { @Override public void visit(Cat cat) { System.out.println(“Кошка покормлена!”); cat.meow(); // Кошка мяукает после кормления } @Override public void visit(Dog dog) { System.out.println(“Собака покормлена!”); dog.bark(); // Собака лает после кормления } }

При переборе массива каждое животное принимает посетителя через метод accept(). В свою очередь, посетитель вызывает соответствующий метод visit() для конкретного типа животного. В результате каждое животное получает порцию еды и выражает нам за это искреннюю благодарность:

Кошка покормлена! Мяу! Собака покормлена! Гав!

Что дальше

Сегодня вы в общих чертах познакомились с одной из обширных тем в мире программирования. Для более глубокого изучения рекомендуем книгу «Приёмы объектно-ориентированного проектирования» — классическое руководство по этой теме. В народе её авторов — Эриха Гамму, Ричарда Хелма, Ральфа Джонсона и Джона Влиссидеса — называют «Банда четырёх». В книге подробно разобраны 23 паттерна проектирования, с описанием их возможностей и ограничений.

После изучения классики рекомендуем обратиться к книге «Head First. Паттерны проектирования». В ней представлен современный взгляд на 14 основных паттернов через понятные жизненные примеры и наглядные иллюстрации. Авторы книги — новая «банда четырёх»: Эрик Фримен, Элизабет Робсон, Кэти Сьерра и Берт Бейтс. Также советуем заглянуть на сайт Refactoring.Guru, где собраны 22 паттерна с примерами и псевдокодом.

Под термином «Фабрика» мы подразумеваем общее название для группы паттернов, которые связаны с созданием объектов. В эту группу входят: «Простая фабрика», «Фабричный метод» и «Абстрактная фабрика».

Инкапсулирует — значит скрывает внутренние детали реализации, предоставляя только необходимый интерфейс. Например, вам не нужно понимать, как устроен пульт от телевизора — достаточно нажать кнопку, чтобы переключить канал. Точно так же паттерн «Фабрика» скрывает сложную логику создания объектов за простым вызовом метода.

Похожие статьи