PATTERNS Flashcards
Что такое «шаблон проектирования»?
Паттерны проектирования (шаблоны проектирования) - это стандартные способы решения часто возникающих задач.
Это проверенное и готовое к использованию архитектурное решение не привязанное к конкретному языку.
Это не класс, не библиотека, не фреймворк, которые можно подключить к проекту.
Это только общие принципы, которые надо уметь правильно применить под нашу программу
Но тем не менее паттерны используются в реализацях библиотек и в фреймфорках
Паттерны часто путают с алгоритмами, ведь оба понятия описывают типовые решения каких-то известных проблем. Но если алгоритм — это чёткий набор действий, то паттерн — это высокоуровневое описание решения, реализация которого может отличаться в двух разных программах.
Плюсы:
* упрощение разработки за счет готовых абстракций, Шаблоны проектирования уже проверены временем и опытом, поэтому использование их может уменьшить количество ошибок и упростить разработку.
* облегчение коммуникации между разработчиками.
* Увеличение переносимости - Шаблоны проектирования могут быть использованы в разных проектах и на разных языках программирования, что позволяет увеличить переносимость кода.
* Знание ООП и принципов ООП не позволит писать хороший код, нужно применять паттерны, которые были сформулированы на принципах ООП
Минусы:
* слепое следование некоторому шаблону может привести к усложнению программы;
* желание попробовать некоторый шаблон в деле без особых на то оснований.
* Перфекционизм - Разработчики могут стать слишком зависимыми от шаблонов проектирования и стараться применять их везде, даже там, где это не нужно, что может привести к избыточности кода.
Зачем знать паттерны?
Вы можете вполне успешно работать, не зная ни одного паттерна. Более того, вы могли уже не раз реализовать какой-то из паттернов, даже не подозревая об этом.
Но осознанное владение инструментом как раз и отличает профессионала от любителя. Вы можете забить гвоздь молотком, а можете и дрелью, если сильно постараетесь. Но профессионал знает, что главная фишка дрели совсем не в этом. Итак, зачем же знать паттерны?
- Проверенные решения. Вы тратите меньше времени, используя готовые решения, вместо повторного изобретения велосипеда. До некоторых решений вы смогли бы додуматься и сами, но многие могут быть для вас открытием.
- Стандартизация кода. Вы делаете меньше просчётов при проектировании, используя типовые унифицированные решения, так как все скрытые проблемы в них уже давно найдены.
- Общий программистский словарь. Вы произносите название паттерна, вместо того, чтобы час объяснять другим программистам, какой крутой дизайн вы придумали и какие классы для этого нужны.
Назовите основные характеристики шаблонов.
имя – все шаблоны имеют уникальное имя, служащее для их идентификации;
назначение данного шаблона;
задача, которую шаблон позволяет решить;
способ решения, предлагаемый в шаблоне для решения задачи в том контексте, где этот шаблон был найден;
участники – сущности, принимающие участие в решении задачи;
следствия от использования шаблона как результат действий, выполняемых в шаблоне;
реализация – возможный вариант реализации шаблона.
Назовите три основные группы паттернов.
Порождающие – абстрагируют процесс создание экземпляра, делегируя этот процесс другому объекту.
Они позволяют сделать систему независимой от способа создания и композиции объектов и избежать внесения в программу лишних зависимостей.
* Фабричный метод (Factory method) - определяет интерфейс для создания объектов, но позволяет подклассам выбрать класс создаваемого экземпляра.
* Абстрактная фабрика (Abstract factory) - предоставляет интерфейс для создания связанных объектов, без указания конкретных классов.
* Одиночка (Singleton) - гарантирует, что у класса есть только один экземпляр, и обеспечивает доступ к нему через глобальную точку доступа.
* Строитель (Builder) - разделяет процесс создания сложного объекта на более простые шаги и обеспечивает контроль над этим процессом.
* Прототип (Prototype) - используется для создания новых объектов путем копирования существующих объектов.
Структурные – отвечают за построение удобной в поддержке системы зависимостей между объектами с целью создания более функциональных структур.
* Адаптер (Adapter) - позволяет интерфейсу одного класса быть использованным как интерфейс другого класса.
* Мост (Bridge) - разделяет абстракцию от ее реализации, позволяя им изменяться независимо друг от друга.
* Декоратор (Decorator) - динамически добавляет новую функциональность к объекту, не изменяя его исходного класса.
* Фасад (Facade) - предоставляет простой интерфейс для сложной системы, упрощая взаимодействие клиентов с системой.
* Компоновщик (Composite) - позволяет обрабатывать отдельные объекты и их группы одинаково.
Поведенческие – заботятся об эффективной коммуникации между объектами.
* Цепочка ответственности (Chain of Responsibility) - позволяет передавать запросы по цепочке объектов, пока один из них не обработает запрос.
* Команда (Command) - инкапсулирует запрос в виде объекта, что позволяет задавать параметры для запроса, ставить запросы в очередь и отменять операции.
* Итератор (Iterator) - предоставляет способ последовательного доступа к элементам объекта без раскрытия его внутренней структуры.
* Состояние (State) - позволяет объекту изменять свое поведение в зависимости от своего внутреннего состояния
* Наблюдатель (Observer) - позволяет объектам оповещать другие объекты об изменениях своего состояния, не зная, какие объекты конкретно будут оповещены.
Паттерны архитектуры (Architectural patterns):
Архитектурные паттерны (Architectural patterns) - определяют общую архитектуру системы и ее компонентов. Они используются для определения общей структуры приложения.
- Модель-вид-контроллер (Model-View-Controller, MVC) - разделяет приложение на три основные компоненты: модель данных, представление и контроллер, обеспечивая лучшую организацию и управляемость кода.
- Слой сервисов (Service Layer) - представляет собой слой абстракции, который обеспечивает логику приложения и взаимодействие между уровнями, скрывая подробности реализации от других слоев.
- Фронт-контроллер (Front Controller) - представляет собой компонент, который обрабатывает все запросы пользователя, прежде чем они будут переданы на обработку другим компонентам приложения.
- Инверсия управления (Inversion of Control, IoC) - представляет собой шаблон проектирования, который переносит управление созданием и связыванием объектов на специальный контейнер (IoC-контейнер), чтобы снизить связанность между компонентами и упростить тестирование и расширение кода.
- Микросервисы (Microservices) - представляют собой архитектурный подход, при котором приложение разбивается на множество маленьких сервисов, каждый из которых отвечает за конкретную функциональность и может разрабатываться и масштабироваться независимо от других сервисов.
Основные – основные строительные блоки, используемые для построения других шаблонов. Например, интерфейс.
Расскажите про паттерн Одиночка (Singleton).
Singleton pattern в Java - это порождающий шаблон проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
Реализация Singleton в Java включает в себя создание приватного конструктора класса, чтобы запретить создание экземпляров класса извне, а также создание статической переменной, которая будет хранить единственный экземпляр класса. Затем создается статический метод, getInstance(). Он либо создает объект, либо возвращает существующий объект, если он уже был создан.
public class Singleton { private static Singleton instance; private Singleton() {} static { instance = new Singleton(); } public static Singleton getInstance() { return instance; } }
Singleton может использоваться в различных областях, например:
- Работа с конфигурационными файлами - Singleton может быть использован для загрузки конфигурационных данных в приложении, что обеспечивает единственную точку доступа к этим данным во всем приложении.
- Работа с базой данных - Singleton может быть использован для создания единственного подключения к базе данных, что обеспечивает более эффективное использование ресурсов и улучшает производительность приложения.
- Создание логгера - Singleton может быть использован для создания логгера, который будет использоваться во всем приложении. Это позволяет управлять уровнем логирования и записью логов в единственном месте.
- **Кэширование данных **- Singleton может быть использован для создания кэша данных, который будет использоваться во всем приложении. Это позволяет уменьшить время доступа к данным и улучшить производительность приложения.
- Создание фабрики объектов - Singleton может быть использован для создания фабрики объектов, которая будет создавать объекты определенного типа во всем приложении.
Некоторые из фреймворков, которые используют Singleton:
* Spring Framework - Singleton используется по умолчанию для создания бинов (объектов), которые управляются контейнером Spring.
* Hibernate - Singleton может использоваться для создания фабрики сессий, которая будет использоваться для создания сессий для работы с базой данных.
* Log4j - Singleton используется для создания объекта логгера, который будет использоваться для записи логов в приложении.
* Junit - Singleton может использоваться для создания объекта TestRunner, который будет использоваться для запуска тестов в Junit.
* Apache Commons Pool - Singleton может использоваться для создания пула объектов, который будет использоваться для повторного использования объектов и уменьшения нагрузки на систему.
Плюсы использования паттерна Singleton в Java:
- Гарантирует наличие единственного экземпляра класса.
- Предоставляет глобальную точку доступа к этому экземпляру, что упрощает управление объектами.
- Позволяет использовать ленивую инициализацию, т.е. создание объекта только при первом вызове метода getInstance(), что может быть полезно для экономии ресурсов.
- Можно наследовать Singleton-классы, что упрощает создание подклассов с единственным экземпляром.
Минусы использования паттерна Singleton в Java:
- Уменьшает гибкость кода, т.к. создание единственного экземпляра класса жестко заложено в коде, и нельзя создать дополнительные экземпляры, если это понадобится.
- Может привести к созданию глобального состояния, которое может быть изменено в любом месте программы, что усложняет отладку и тестирование приложения.
- Может привести к проблемам с многопоточностью, если не обеспечена правильная синхронизация доступа к единственному экземпляру класса.
- Нарушает принцип единой ответственности, так как
1) Несет функционал
2) Гарантирует наличие одной сущности
3) Обеспечивает единую точку доступа к этой сущности - Требует постоянного создания Mock-объектов при юнит-тестировании.
Какие реализации паттерна Singleton вы знаете?
Жадные:
1) Статическое поле и метод доступа (Static field and accessor)
2) Объект перечисления (Enum)
Ленивые:
1) Синхронизированный метод доступа (Synchronized accessor)
2) Блокировка с двойной проверкой и volatile (Double-checked locking + volatile)
3) Внутренний статический класс Holder (On demand Holder).
Шаги реализации
1. Добавьте в класс приватное статическое поле, которое будет содержать одиночный объект.
2. Объявите статический создающий метод, который будет использоваться для получения одиночки.
3. Добавьте «ленивую инициализацию» (создание объекта при первом вызове метода) в создающий метод одиночки.
4. Сделайте конструктор класса приватным.
5. В клиентском коде замените вызовы конструктора одиночка вызовами его создающего метода.
Расскажите про паттерн Строитель (Builder).
Паттерн Строитель (Builder) - это Порождающий паттерн проектирования, который используется для создания сложных объектов с различными свойствами. Он позволяет создавать объекты пошагово, обеспечивая контроль над каждой стадией создания объекта. А значит, больше не нужно пытаться «запихнуть» в конструктор все возможные опции продукта.
Паттерн предлагает вынести конструирование объекта за пределы его собственного класса, поручив это дело отдельным объектам, называемым строителями.
Название паттерна: Строитель (Builder).
Назначение: паттерн Строитель используется для создания объектов сложной структуры, позволяя разбить процесс создания на более мелкие шаги и контролировать его выполнение.
Задача, которую шаблон позволяет решить: шаблон Строитель позволяет создавать объекты, состоящие из множества взаимосвязанных компонентов, при этом упрощая процесс их создания и обеспечивая гибкость в выборе компонентов и порядке их создания.
Способ решения: для решения задачи создания сложных объектов, паттерн Строитель предлагает создание отдельного класса-строителя (Builder), который содержит методы для пошагового создания объекта. Чтобы создать объект, вам нужно поочерёдно вызывать методы строителя. Причём не нужно запускать все шаги, а только те, что нужны для производства объекта определённой конфигурации.
Кроме того, для создания объекта используется класс-директор (Director), который определяет порядок вызова методов класса-строителя.
Участники:
* класс-строитель (Builder)
* класс-директор (Director)
* конечный объект (Product), который является результатом работы шаблона.
Следствия от использования шаблона: использование паттерна Строитель позволяет создавать объекты сложной структуры, обеспечивая гибкость в выборе компонентов и порядке их создания. Кроме того, шаблон упрощает процесс создания объектов и уменьшает зависимость между компонентами объекта. Это позволяет создавать более гибкие и масштабируемые системы.
Вы можете пойти дальше и выделить вызовы методов строителя в отдельный класс, называемый директором. В этом случае директор будет задавать порядок шагов строительства, а строитель — выполнять их.
Директор (Director) - это часть паттерна Строитель (Builder), который управляет процессом конструирования объекта, используя объект-строитель. Он определяет порядок вызова методов строителя для создания сложного объекта, что позволяет упростить процесс конструирования.
В Java директор может быть реализован как отдельный класс, который получает объект-строитель в качестве аргумента конструктора и вызывает его методы по порядку.
Использование директора позволяет упростить процесс конструирования сложного объекта, так как клиентский код может просто создать объект директора и вызывать его методы для создания объектов, не беспокоясь о деталях конструирования. Также это позволяет улучшить читаемость кода, так как последовательность вызовов методов строителя определяется в одном месте - в директоре.
Шаги реализации:
1. Убедитесь в том, что создание разных представлений объекта можно свести к общим шагам.
2. Опишите эти шаги в общем интерфейсе строителей.
3. Для каждого из представлений объекта-продукта создайте по одному классу-строителю и реализуйте их методы строительства.
4. Не забудьте про метод получения результата. Обычно конкретные строители определяют собственные методы получения результата строительства. Вы не можете описать эти методы в интерфейсе строителей, поскольку продукты не обязательно должны иметь общий базовый класс или интерфейс. Но вы всегда сможете добавить метод получения результата в общий интерфейс, если ваши строители производят однородные продукты с общим предком.
5. Подумайте о создании класса директора. Его методы будут создавать различные конфигурации продуктов, вызывая разные шаги одного и того же строителя.
6. Клиентский код должен будет создавать и объекты строителей, и объект директора. Перед началом строительства клиент должен связать определённого строителя с директором. Это можно сделать либо через конструктор, либо через сеттер, либо подав строителя напрямую в строительный метод директора.
7. Результат строительства можно вернуть из директора, но только если метод возврата продукта удалось поместить в общий интерфейс строителей. Иначе вы жёстко привяжете директора к конкретным классам строителей.
Преимущества
* Позволяет создавать продукты пошагово.
* Позволяет использовать один и тот же код для создания различных продуктов.
* Изолирует сложный код сборки продукта от его основной бизнес-логики.
Недостатки
- Усложняет код программы из-за введения дополнительных классов.
- Клиент будет привязан к конкретным классам строителей, так как в интерфейсе директора может не быть метода получения результата.
Расскажите про паттерн Фабричный метод (Factory Method).
Фабричный метод — это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
Таким образом, Фабричный Метод делегирует операцию создания экземпляра субклассам.
Переопределенный метод в каждом наследнике возвращает нужный вариант объекта.
В основе этого паттерна лежит идея использования абстрактного класса или интерфейса для определения метода, который будет создавать объекты. Конкретная реализация создания объекта возлагается на подклассы.
Объекты всё равно будут создаваться при помощи new, но делать это будет фабричный метод. Таким образом можно переопределить фабрчный метод в подклассе, чтобы изменить тип создаваемого продукта.
Допустим, у нас есть некоторый суперкласс, который определяет интерфейс для создания объектов, но сам не определяет конкретный класс объекта. Вместо этого он оставляет реализацию создания объекта подклассам, которые наследуются от этого суперкласса.
В таком случае, каждый подкласс может определить свой собственный класс объекта, который будет создаваться. Это позволяет подклассам изменять тип создаваемых объектов. Таким образом, мы можем создавать объекты разных типов, используя один и тот же интерфейс создания объектов.
Например, представьте, что у нас есть суперкласс “Фабрика пиццы”, который определяет интерфейс для создания пиццы. Мы можем создать подклассы, которые реализуют этот интерфейс, и каждый из них может определять свой собственный тип пиццы. Таким образом, мы можем создавать различные типы пицц, используя один и тот же интерфейс “Фабрика пиццы”.
Фабричный метод применяется, когда заранее неизвестны типы и зависимости объектов, с которыми должен работать ваш код.
Фабричный метод отделяет код производства продуктов от остального кода, который эти продукты использует.
Имя: Фабричный метод (Factory Method).
Назначение: позволяет создавать объекты, не указывая конкретный класс, а определяя интерфейс для создания объекта в суперклассе, но позволяя подклассам изменять тип создаваемого объекта.
Задача, которую шаблон позволяет решить: избавиться от жесткой привязки к конкретным классам при создании объектов и дать возможность создавать объекты различных классов, реализующих общий интерфейс.
Способ решения: создание интерфейса фабрики (Factory), который определяет метод для создания объекта. Затем каждый подкласс реализует этот метод, чтобы создавать объекты нужного класса. Таким образом, подклассы определяют тип создаваемого объекта.
Участники:
- Product (Продукт) – интерфейс для объектов, которые фабрика создает.
- ConcreteProduct (Конкретный продукт) – классы, которые реализуют интерфейс Product.
- Creator (Создатель) – абстрактный класс или интерфейс, который определяет метод FactoryMethod для создания объекта. Этот метод может быть абстрактным, либо иметь реализацию по умолчанию.
- ConcreteCreator (Конкретный создатель) – классы, которые наследуются от Creator и реализуют FactoryMethod для создания объектов.
**Следствия от использования шаблона: **позволяет улучшить расширяемость кода, избежать жестких зависимостей от конкретных классов, а также повысить уровень абстракции в приложении. При этом может возникнуть некоторая избыточность кода, так как для каждого нового типа продукта нужно создавать новый подкласс ConcreteProduct и ConcreteCreator.
Шаги реализации
1. Приведите все создаваемые продукты к общему интерфейсу.
2. В классе, который производит продукты, создайте пустой фабричный метод. В качестве возвращаемого типа укажите общий интерфейс продукта.
3. Затем пройдитесь по коду класса и найдите все участки, создающие продукты. Поочерёдно замените эти участки вызовами фабричного метода, перенося в него код создания различных продуктов.
В фабричный метод, возможно, придётся добавить несколько параметров, контролирующих, какой из продуктов нужно создать.
На этом этапе фабричный метод, скорее всего, будет выглядеть удручающе. В нём будет жить большой условный оператор, выбирающий класс создаваемого продукта. Но не волнуйтесь, мы вот-вот исправим это.
4. Для каждого типа продуктов заведите подкласс и переопределите в нём фабричный метод. Переместите туда код создания соответствующего продукта из суперкласса.
5. Если создаваемых продуктов слишком много для существующих подклассов создателя, вы можете подумать о введении параметров в фабричный метод, которые позволят возвращать различные продукты в пределах одного подкласса.
Например, у вас есть класс Почта с подклассами АвиаПочта и НаземнаяПочта, а также классы продуктов Самолёт, Грузовик и Поезд. Авиа соответствует Самолётам, но для НаземнойПочты есть сразу два продукта. Вы могли бы создать новый подкласс почты для поездов, но проблему можно решить и по-другому. Клиентский код может передавать в фабричный метод НаземнойПочты аргумент, контролирующий тип создаваемого продукта.
6. Если после всех перемещений фабричный метод стал пустым, можете сделать его абстрактным. Если в нём что-то осталось — не беда, это будет его реализацией по умолчанию.
Фабричный метод можно рассматривать как частный случай Шаблонного метода. Кроме того, Фабричный метод нередко бывает частью большого класса с Шаблонными методами.
Преимущества использования паттерна Фабричный метод в Java:
- уменьшение связности между классами, т.к. создание объекта вынесено в отдельный класс;
- возможность добавлять новые подклассы для создания объектов, не изменяя код клиентского класса;
- повышение гибкости приложения за счет использования интерфейса.
- инкапсулирует создание объектов, предлагая единый интерфейс для этого процесса и тип возвращаемого объекта может варьироваться в зависимости от входных параметров.
- Реализует принцип открытости/закрытости.
Недостатки использования паттерна Фабричный метод в Java:
* не масштабируются для большого кол-ва необязательных параметров.
Зачем фабрики оформляются в виде статических методов?
Чтобы метод create можно было вызывать и без создания экземпляра объекта. С другой стороны,
теряется возможность субклассирования и изменения поведения метода create.
Пример: SessionFactory в Hibernate.
Расскажите про паттерн Абстрактная фабрика (Abstract Factory).
Шаблон “абстрактная фабрика” (Abstract Factory) является порождающим паттерном проектирования, который предоставляет интерфейс для создания семейств связанных объектов без указания их конкретных классов. Он позволяет создавать объекты с определенными интерфейсами, не привязываясь к их конкретным реализациям, тем самым обеспечивая гибкость и удобство при изменении логики создания объектов.
Абстрактная фабрика создает интерфейс, группирующий остальные фабрики, которые логически связанны друг с другом. Это своег орода абстракция для фабрики и фабричного метода
Имя: Abstract Factory
Назначение: Предоставляет интерфейс для создания семейств связанных объектов без указания их конкретных классов.
Задача: Обеспечить создание семейства связанных объектов, при этом не зависеть от их конкретных реализаций.
Способ решения: Абстрактная фабрика определяет интерфейс для создания семейств связанных объектов, каждый из которых имеет общий интерфейс, но может иметь свою конкретную реализацию.
Участники:
- Абстрактная фабрика: определяет интерфейс для создания объектов.
- Конкретная фабрика: реализует интерфейс Абстрактной фабрики для создания конкретных объектов.
- Абстрактный продукт: определяет общий интерфейс продуктов, создаваемых Абстрактной фабрикой.
- Конкретный продукт: реализует интерфейс Абстрактного продукта.
Следствия: Использование паттерна “абстрактная фабрика” обеспечивает гибкость и удобство при изменении логики создания объектов, позволяет создавать объекты с определенными интерфейсами, не привязываясь к их конкретным реализациям.
Абстрактная фабрика определяет интерфейс для создания семейства связанных объектов, таких как объекты пиццы и их ингредиенты. Конкретные фабрики реализуют этот интерфейс и создают конкретные объекты. Таким образом, использование Абстрактной фабрики позволяет легко заменять семейства продуктов в зависимости от контекста приложения.
Применимость
Когда бизнес-логика программы должна работать с разными видами связанных друг с другом продуктов, не завися от конкретных классов продуктов.
- Абстрактная фабрика скрывает от клиентского кода подробности того, как и какие конкретно объекты будут созданы. Но при этом клиентский код может работать со всеми типами создаваемых продуктов, поскольку их общий интерфейс был заранее определён.
Когда в программе уже используется Фабричный метод, но очередные изменения предполагают введение новых типов продуктов.
- В хорошей программе каждый класс отвечает только за одну вещь. Если класс имеет слишком много фабричных методов, они способны затуманить его основную функцию. Поэтому имеет смысл вынести всю логику создания продуктов в отдельную иерархию классов, применив абстрактную фабрику.
Шаги реализации:
1. Создайте таблицу соотношений типов продуктов к вариациям семейств продуктов.
2. Сведите все вариации продуктов к общим интерфейсам.
3. Определите интерфейс абстрактной фабрики. Он должен иметь фабричные методы для создания каждого из типов продуктов.
4. Создайте классы конкретных фабрик, реализовав интерфейс абстрактной фабрики. Этих классов должно быть столько же, сколько и вариаций семейств продуктов.
5. Измените код инициализации программы так, чтобы она создавала определённую фабрику и передавала её в клиентский код.
6. Замените в клиентском коде участки создания продуктов через конструктор вызовами соответствующих методов фабрики.
Преимущества
* Гарантирует сочетаемость создаваемых продуктов.
* Избавляет клиентский код от привязки к конкретным классам продуктов.
* Выделяет код производства продуктов в одно место, упрощая поддержку кода.
* Упрощает добавление новых продуктов в программу.
* Реализует принцип открытости/закрытости.
Недостатки
* Усложняет код программы из-за введения множества дополнительных классов.
* Требует наличия всех типов продуктов в каждой вариации.
Отличия между фабричным методом и абстрактной фабрикой
- Фабричный метод создает один продукт, а Абстрактная фабрика создает семейство связанных продуктов.
- Фабричный метод использует наследование, чтобы делегировать создание конкретных продуктов дочерним классам, а Абстрактная фабрика использует композицию объектов для создания продуктов.
- Фабричный метод может быть реализован в виде статического метода, а Абстрактная фабрика требует создания объекта фабрики для создания продуктов.
- Фабричный метод создает продукт из одного класса, а Абстрактная фабрика создает продукты из нескольких взаимосвязанных классов.
- Фабричный метод может быть переопределен в подклассах для создания различных вариаций продуктов, а Абстрактная фабрика может быть расширена путем добавления новых классов продуктов и соответствующих фабрик.
Расскажите про паттерн Прототип (Prototype).
Порождающий паттерн проектирования, который позволяет копировать объекты, не вдаваясь в подробности их реализации.
Он вводит общий интерфейс с методом clone для всех объектов, поддерживающих клонирование.
Реализация этого метода в разных классах очень схожа.
Метод создаёт новый объект текущего класса и копирует в него значения всех полей собственного объекта.
Имя: Прототип (Prototype)
Назначение: Предоставление механизма создания объектов на основе готовых прототипов, что упрощает создание объектов и уменьшает затраты на производство.
Задача: Шаблон Прототип решает задачу создания объектов без явного указания класса, а также уменьшает затраты на создание объектов за счет использования уже готовых прототипов, что уменьшает время и увеличивает эффективность разработки.
Способ решения: Шаблон Прототип предлагает создание интерфейса-прототипа, который определяет метод клонирования объекта. Затем создаются классы, реализующие этот интерфейс, которые могут создавать новые объекты на основе уже существующих прототипов путем клонирования.
Участники:
- Клиент: создает новые объекты, используя прототипы.
- Прототип: определяет интерфейс клонирования объектов.
- Конкретный прототип: реализует интерфейс клонирования объекта и содержит конкретную реализацию клонирования объекта.
Следствия от использования шаблона:
* Уменьшение затрат на производство объектов путем использования готовых прототипов.
* Увеличение эффективности разработки благодаря упрощенному процессу создания объектов.
* Гибкость в создании новых объектов, которые могут быть созданы на основе уже существующих прототипов.
* Возможность создания новых объектов без необходимости явного указания класса.
* Возможность изменения поведения создаваемых объектов путем изменения конкретных прототипов.
Шаги реализации паттерна Прототип (Prototype) могут быть следующими:
- Создать абстрактный класс или интерфейс, который определяет метод клонирования объекта. Обычно этот метод должен создавать копию объекта и возвращать ее.
- Создать класс-прототип, который реализует интерфейс клонирования и представляет собой объект, который нужно клонировать.
- Если требуется, создать несколько конкретных классов-прототипов, которые наследуются от базового класса-прототипа.
- Создать класс, который будет использовать прототипы для создания новых объектов. Этот класс будет содержать методы для регистрации прототипов и создания новых объектов на основе этих прототипов.
- В клиентском коде создать объекты, используя методы класса, созданного на шаге 4.
- Проверить, что созданные объекты идентичны прототипам, которые использовались для их создания.
Плюсы использования паттерна Прототип:
- Уменьшение количества классов. Когда нужно создавать большое количество объектов с похожими свойствами, использование шаблона Прототип может значительно уменьшить количество классов в программе, так как не нужно создавать классы для каждого конкретного объекта.
- Позволяет клонировать объекты, не привязываясь к их конкретным классам.
- Ускорение создания объектов. Использование шаблона Прототип позволяет ускорить создание объектов за счет клонирования уже существующих объектов.
- Гибкость. Шаблон Прототип позволяет динамически создавать новые объекты на основе уже существующих, что обеспечивает гибкость и удобство в работе.
Минусы использования паттерна Прототип:
- Сложно клонировать составные объекты, имеющие ссылки на другие объекты.
- Проблемы с безопасностью. Если объекты содержат конфиденциальную информацию, клонирование может привести к раскрытию этой информации. Эту проблему можно решить путем реализации защиты конфиденциальных данных в клонированном объекте.
Расскажите про паттерн Адаптер (Adapter).
Адаптер — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Это объект-переводчик, который трансформирует интерфейс или данные одного объекта в такой вид, чтобы он стал понятен другому объекту.
При этом адаптер оборачивает один из объектов, так что другой объект даже не знает о наличии первого.
1) Адаптер имеет интерфейс, который совместим с первым объектом и содержит второй объект.
2) Поэтому на объекте адаптера можно вызывать методы первого объекта.
3) Адаптер получает эти вызовы и перенаправляет их второму объекту, но уже в том формате и последовательности, которые понятны второму объекту.
Назначение: Паттерн Адаптер позволяет объединять несовместимые интерфейсы, позволяя объектам работать вместе, которые ранее не могли взаимодействовать из-за несовместимости их интерфейсов.
Задача, которую шаблон позволяет решить: Паттерн Адаптер решает проблему несовместимости интерфейсов, позволяя работать вместе объектам, которые имеют различные интерфейсы. Это может быть полезно, например, при использовании кода сторонних библиотек, которые имеют свои интерфейсы, а также при создании расширений и дополнений к уже существующим приложениям.
Способ решения, предлагаемый в шаблоне для решения задачи в том контексте, где этот шаблон был найден: Паттерн Адаптер реализуется путем создания класса-адаптера, который обеспечивает совместимость интерфейсов. Адаптер наследует интерфейс, который должен быть использован и реализует интерфейс, который должен быть адаптирован. После этого адаптер принимает вызовы от клиента и преобразует их в соответствующие вызовы к адаптируемому объекту.
Участники:
* Клиент: объект, который использует другой объект через интерфейс.
* Интерфейс, который должен быть использован клиентом.
* Адаптируемый объект: объект, который должен быть использован клиентом, но имеет неподходящий интерфейс.
* Адаптер: класс, который реализует интерфейс, который должен быть использован клиентом, и преобразует вызовы к адаптируемому объекту в соответствующие вызовы.
Следствия от использования шаблона как результат действий, выполняемых в шаблоне: Использование паттерна Адаптер позволяет снизить затраты на разработку новых интерфейсов и упрощает миграцию с одного интерфейса на другой. Он также позволяет повторно использовать старый код и интегрировать различные компоненты системы. Однако, паттерн Адаптер может привести к увеличению сложности кода и снижению производительности в некоторых случаях, поэтому его использование следует осуществлять с осторожностью.
Шаги реализации
1. Убедитесь, что у вас есть два класса с несовместимыми интерфейсами:
- полезный сервис — служебный класс, который вы не можете изменять (он либо сторонний, либо от него зависит другой код);
- один или несколько клиентов — существующих классов приложения, несовместимых с сервисом из-за неудобного или несовпадающего интерфейса.
- Опишите клиентский интерфейс, через который классы приложения смогли бы использовать класс сервиса.
- Создайте класс адаптера, реализовав этот интерфейс.
- Поместите в адаптер поле, которое будет хранить ссылку на объект сервиса. Обычно это поле заполняют объектом, переданным в конструктор адаптера. В случае простой адаптации этот объект можно передавать через параметры методов адаптера.
- Реализуйте все методы клиентского интерфейса в адаптере. Адаптер должен делегировать основную работу сервису.
- Приложение должно использовать адаптер только через клиентский интерфейс. Это позволит легко изменять и добавлять адаптеры в будущем.
Преимущества
* Отделяет и скрывает от клиента подробности преобразования различных интерфейсов.
Недостатки
* Усложняет код программы из-за введения дополнительных классов.
Ниже приведены некоторые примеры использования Адаптера в Java:
- java.io.InputStreamReader - это пример класса-адаптера, который преобразует InputStream в Reader.
- java.util.Arrays#asList() - метод asList() использует адаптер для преобразования массива в список.
- java.util.Collections#list()- метод list() также использует адаптер для преобразования массива в список.
- java.util.stream.Stream - этот класс предоставляет методы адаптеры для преобразования коллекций, массивов и других источников данных в поток данных.
Расскажите про паттерн Декоратор (Decorator).
Декоратор — это структурный паттерн проектирования, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки».
Класс декоратор и декорируемый класс имплементируют общий интерфейс и декоратор содержит декорируемый объект.
Также есть классы, расширяющие декоратор, они и добавляют свою реализацию методов интерфейса к реализации этого метода декорируемым объектом.
Имя: Декоратор (Decorator).
Назначение: Паттерн Декоратор позволяет динамически добавлять новые функциональные возможности объекту без изменения его основной структуры.
Задача: Декоратор используется для расширения функциональности объектов без необходимости изменять сам объект. Это особенно полезно в тех случаях, когда необходимо добавить новое поведение объекту, но не хочется создавать множество подклассов для каждой комбинации поведения.
Способ решения:
Декоратор работает путем создания новых классов-декораторов, которые оборачивают базовый объект и добавляют ему новую функциональность. Каждый декоратор имеет свой собственный набор полей и методов, которые добавляют новую функциональность. Декоратор может быть цепочкой, где один декоратор оборачивает другой декоратор и так далее.
Участники:
* Компонент (Component): это базовый класс, который определяет интерфейс для всех объектов, которые могут быть декорированы.
* Конкретный компонент (Concrete Component): это базовый объект, который может быть декорирован.
* Декоратор (Decorator): это базовый класс для всех декораторов. Он содержит ссылку на объект типа Component и определяет интерфейс, который соответствует интерфейсу Component.
* Конкретный декоратор (Concrete Decorator): это классы-декораторы, которые добавляют новую функциональность объекту.
Следствия от использования шаблона:
Использование паттерна Декоратор позволяет добавлять новую функциональность объекту, не изменяя его основной структуры. Это упрощает поддержку кода, позволяет избежать создания множества подклассов для каждой комбинации поведения. Кроме того, Декоратор улучшает модульность кода, поскольку каждый декоратор может быть использован независимо от других. Однако следует учитывать, что использование слишком многих декораторов может привести к перегрузке объекта.
Шаги реализации
1. Убедитесь, что в вашей задаче есть один основной компонент и несколько опциональных дополнений или надстроек над ним.
2. Создайте интерфейс компонента, который описывал бы общие методы как для основного компонента, так и для его дополнений.
3. Создайте класс конкретного компонента и поместите в него основную бизнес-логику.
4. Создайте базовый класс декораторов. Он должен иметь поле для хранения ссылки на вложенный объект-компонент. Все методы базового декоратора должны делегировать действие вложенному объекту.
5. И конкретный компонент, и базовый декоратор должны следовать одному и тому же интерфейсу компонента.
6. Теперь создайте классы конкретных декораторов, наследуя их от базового декоратора. Конкретный декоратор должен выполнять свою добавочную функцию, а затем (или перед этим) вызывать эту же операцию обёрнутого объекта.
7. Клиент берёт на себя ответственность за конфигурацию и порядок обёртывания объектов.
Преимущества
* Большая гибкость, чем у наследования.
* Позволяет добавлять обязанности на лету.
* Можно добавлять несколько новых обязанностей сразу.
* Позволяет иметь несколько мелких объектов вместо одного объекта на все случаи жизни.
Недостатки
* Трудно конфигурировать многократно обёрнутые объекты.
* Обилие крошечных классов.
Расскажите про паттерн Заместитель (Proxy).
Заместитель — это структурный паттерн проектирования, который позволяет подставлять вместо реальных объектов специальные объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова оригиналу.
Клиент не знает, что общается с прокси, он просто обращается к объкту общего для прокси и базового объекта интерфейсу.
Заместитель предлагает создать новый класс-дублёр, имеющий тот же интерфейс, что и оригинальный служебный объект. При получении запроса от клиента объект-заместитель сам бы создавал экземпляр служебного объекта, выполняя промежуточную логику, которая выполнялась бы до (или после) вызовов этих же методов в настоящем объекте.
Имя: Заместитель (Proxy).
Назначение: Паттерн Заместитель используется для контроля доступа к объектам, предоставляя суррогатный объект, который может управлять доступом к реальному объекту.
Задача: Паттерн Заместитель позволяет решить задачу контроля доступа к объекту, когда необходимо ограничить доступ к реальному объекту или отложить его создание и загрузку до момента, когда это действительно необходимо.
Способ решения: Паттерн Заместитель предлагает создание суррогатного объекта, который выступает в роли заместителя для реального объекта. Этот заместитель может иметь такой же интерфейс, как и реальный объект, что позволяет использовать его вместо него без каких-либо изменений в коде. При этом заместитель может выполнять определенные действия до и после обращения к реальному объекту, например, проверять права доступа или откладывать его создание до момента, когда это будет необходимо.
Участники: В паттерне Заместитель участвуют следующие сущности:
- Subject: определяет общий интерфейс для Proxy и RealSubject. Поэтому Proxy может использоваться вместо RealSubject
- Реальный объект (Real Subject) - объект, доступ к которому ограничивается или откладывается.
- Заместитель (Proxy) - суррогатный объект, который выступает в роли заместителя для реального объекта.
- Клиент (Client) - объект, который использует заместитель для работы с реальным объектом.
Следствия от использования шаблона: Использование паттерна Заместитель может привести к следующим результатам:
- Улучшенная безопасность: позволяет контролировать доступ к реальному объекту и обеспечивать безопасность при работе с ним.
- Уменьшение нагрузки на систему: отложение создания реального объекта до момента, когда это действительно необходимо, позволяет уменьшить нагрузку на систему и ускорить ее работу.
- Увеличение гибкости системы: заместитель может быть использован для изменения поведения реального объекта без необходимости изменять код клиента.
Шаги реализации
1. Определите интерфейс, который бы сделал заместитель и оригинальный объект взаимозаменяемыми.
2. Создайте класс заместителя. Он должен содержать ссылку на сервисный объект. Чаще всего, сервисный объект создаётся самим заместителем. В редких случаях заместитель получает готовый сервисный объект от клиента через конструктор.
3. Реализуйте методы заместителя в зависимости от его предназначения. В большинстве случаев, проделав какую-то полезную работу, методы заместителя должны передать запрос сервисному объекту.
4. Подумайте о введении фабрики, которая решала бы, какой из объектов создавать — заместитель или реальный сервисный объект. Но, с другой стороны, эта логика может быть помещена в создающий метод самого заместителя.
5. Подумайте, не реализовать ли вам ленивую инициализацию сервисного объекта при первом обращении клиента к методам заместителя.
Преимущества
* Позволяет контролировать сервисный объект незаметно для клиента.
* Может работать, даже если сервисный объект ещё не создан.
* Может контролировать жизненный цикл служебного объекта.
Недостатки
* Усложняет код программы из-за введения дополнительных классов.
* Увеличивает время отклика от сервиса.
Какие типы прокси (“заместителей”) вы знаете?
- Протоколирующий (Logging proxy) - используется для регистрации информации об операциях, выполняемых с реальным объектом, например, для отладки и анализа производительности.
- Удалённый (Remote proxy) - используется для доступа к удаленному объекту через сеть, позволяет скрыть детали реализации удаленного объекта и обеспечить безопасный доступ к нему.
- Виртуальный (Virtual proxy) - используется для отложенной(Ленивой) загрузки ресурсоемких объектов, например, изображений или больших файлов, для ускорения работы приложения и экономии ресурсов.
- Copy-on-write proxy - используется для оптимизации копирования объектов, когда копия создается только при изменении оригинала.
- Защищающий (Protection proxy) - используется для ограничения доступа к объекту и контроля его использования, например, для установки прав доступа или ограничения количества экземпляров.
- Кэширующий (Caching proxy) - используется для кэширования результатов выполнения операций, чтобы ускорить повторные запросы к объекту.
- “Умная” ссылка (Smart reference proxy) - используется для добавления дополнительной функциональности ссылкам на объекты, например, для подсчета числа ссылок или автоматической очистки памяти.
Расскажите про паттерн Итератор (Iterator).
Итератор — это поведенческий паттерн проектирования, который даёт возможность последовательно обходить элементы составных объектов (коллекции), не раскрывая конкретную реализацию этих объектов.
Объект-итератор будет отслеживать состояние обхода, текущую позицию в коллекции и сколько элементов ещё осталось обойти. Одну и ту же коллекцию смогут одновременно обходить различные итераторы, а сама коллекция не будет даже знать об этом.
А мы получаем единый интерфейс прохода по элементам, независимо от типа структуры данных.
Идея состоит в том, чтобы вынести поведение обхода коллекции из самой коллекции в отдельный класс.
Детали: Создается итератор и интерфейс, который возвращает итератор. В классе, в котором надо будет вызывать итератор, имплементируем интерфейс, возвращающий итератор, а сам итератор делаем там нестатическим вложенным классом, так как он нигде использоваться больше не будет.
Имя: Итератор (Iterator)
Назначение: Шаблон проектирования Итератор используется для доступа к элементам коллекции последовательно, без раскрытия ее внутренней структуры. Он предоставляет единый интерфейс для обхода различных типов коллекций.
Задача: Итератор позволяет обходить элементы коллекции, не раскрывая ее внутреннюю структуру. Это упрощает работу с коллекциями, уменьшает зависимость между клиентским кодом и коллекциями, а также повышает безопасность работы с данными.
Способ решения: Шаблон Итератор определяет интерфейс Iterator, который объявляет методы для доступа к элементам коллекции. Кроме того, он определяет класс ConcreteIterator, который реализует этот интерфейс и обеспечивает конкретную реализацию доступа к элементам коллекции.
Участники:
- Iterator: определяет интерфейс для доступа к элементам коллекции;
- ConcreteIterator: реализует интерфейс Iterator и обеспечивает конкретную реализацию доступа к элементам коллекции;
- Aggregate: определяет интерфейс для создания объекта-итератора;
- ConcreteAggregate: реализует интерфейс Aggregate и возвращает экземпляр ConcreteIterator.
Следствия от использования шаблона:
* Упрощение работы с коллекциями;
* Уменьшение зависимости между клиентским кодом и коллекциями;
* Повышение безопасности работы с данными;
* Возможность обходить коллекции различных типов, используя единый интерфейс;
* Увеличение гибкости и переносимости кода.
Шаги реализации
1. Создайте общий интерфейс итераторов. Обязательный минимум — это операция получения следующего элемента коллекции. Но для удобства можно предусмотреть и другое. Например, методы для получения предыдущего элемента, текущей позиции, проверки окончания обхода и прочие.
2. Создайте интерфейс коллекции и опишите в нём метод получения итератора. Важно, чтобы сигнатура метода возвращала общий интерфейс итераторов, а не один из конкретных итераторов.
3. Создайте классы конкретных итераторов для тех коллекций, которые нужно обходить с помощью паттерна. Итератор должен быть привязан только к одному объекту коллекции. Обычно эта связь устанавливается через конструктор.
4. Реализуйте методы получения итератора в конкретных классах коллекций. Они должны создавать новый итератор того класса, который способен работать с данным типом коллекции. Коллекция должна передавать ссылку на собственный объект в конструктор итератора.
5. В клиентском коде и в классах коллекций не должно остаться кода обхода элементов. Клиент должен получать новый итератор из объекта коллекции каждый раз, когда ему нужно перебрать её элементы.
Преимущества
Упрощает классы хранения данных.
Позволяет реализовать различные способы обхода структуры данных.
Позволяет одновременно перемещаться по структуре данных в разные стороны.
Недостатки
Не оправдан, если можно обойтись простым циклом.
Расскажите про паттерн Шаблонный метод (Template Method).
Шаблонный Метод это поведенческий паттерн проектирования, который определяет скелет(шаблон) алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. Паттерн позволяет подклассам переопределять шаги алгоритма, не меняя его общей структуры.
Паттерн предлагает разбить алгоритм на последовательность шагов, описать эти шаги в отдельных методах и вызывать их в одном шаблонном методе друг за другом.
Для описания шагов используется абстрактный класс. Общие шаги можно будет описать прямо в абстрактном класе. Это позволит подклассам переопределять некоторые шаги алгоритма, оставляя без изменений его структуру и остальные шаги, которые для этого подкласса не так важны.
Шаблонный метод (Template Method) - это поведенческий паттерн проектирования, который определяет скелет алгоритма, перекладывая некоторые шаги на подклассы. Шаблонный метод позволяет подклассам переопределить определенные шаги алгоритма, не меняя структуры алгоритма в целом.
Имя: Шаблонный метод (Template Method)
Назначение: Определение основных шагов алгоритма, но позволяет подклассам переопределить некоторые шаги этого алгоритма без изменения его структуры в целом.
Задача: Реализация повторяющихся операций с использованием общего алгоритма, но с различными реализациями некоторых шагов алгоритма в каждом подклассе.
Способ решения: Шаблонный метод предлагает определить скелет алгоритма в абстрактном классе, который содержит абстрактные методы, которые должны быть реализованы в подклассах. Конкретные реализации шагов алгоритма перекладываются на подклассы.
Участники:
Абстрактный класс, конкретные классы (подклассы).
Следствия: Шаблонный метод позволяет избежать дублирования кода, упрощает поддержку кода и обеспечивает гибкость и расширяемость системы.
Пример использования шаблонного метода может быть создание игры. В абстрактном классе определяется основной игровой цикл, включающий в себя инициализацию, обновление, отрисовку и окончание игры. Подклассы могут переопределить некоторые шаги этого цикла, такие как отрисовку или обновление объектов, чтобы создать различные виды игр. Таким образом, можно создавать новые игры, используя общий алгоритм игрового цикла, но с различными реализациями для каждой игры.
Шаги реализации
1. Изучите алгоритм и подумайте, можно ли его разбить на шаги. Прикиньте, какие шаги будут стандартными для всех вариаций алгоритма, а какие — изменяющимися.
2. Создайте абстрактный базовый класс. Определите в нём шаблонный метод. Этот метод должен состоять из вызовов шагов алгоритма. Имеет смысл сделать шаблонный метод финальным, чтобы подклассы не могли переопределить его (если ваш язык программирования это позволяет).
3. Добавьте в абстрактный класс методы для каждого из шагов алгоритма. Вы можете сделать эти методы абстрактными или добавить какую-то реализацию по умолчанию. В первом случае все подклассы должны будут реализовать эти методы, а во втором — только если реализация шага в подклассе отличается от стандартной версии.
4. Подумайте о введении в алгоритм хуков. Чаще всего, хуки располагают между основными шагами алгоритма, а также до и после всех шагов.
5. Создайте конкретные классы, унаследовав их от абстрактного класса. Реализуйте в них все недостающие шаги и хуки.
Преимущества
* Облегчает повторное использование кода.
Недостатки
* Вы жёстко ограничены скелетом существующего алгоритма.
* Вы можете нарушить принцип подстановки Барбары Лисков, изменяя базовое поведение одного из шагов алгоритма через подкласс.
* С ростом количества шагов шаблонный метод становится слишком сложно поддерживать.