105.05K
Категория: ИнформатикаИнформатика

Заместитель. Проблема&Решение

1.

Заместитель
структурный паттерн проектирования, который
позволяет подставлять вместо реальных объектов
специальные объекты-заменители.
Эти объекты перехватывают вызовы к оригинальному
объекту, позволяя сделать что-то до или после передачи
вызова оригиналу.

2.

Проблема&Решение
Если существует внешний ресурсоёмкий объект, который нужен не все
время, а изредка.
Можно не создавать этот объект в самом начале программы, а только
когда он кому-то реально понадобится. Каждый клиент объекта получил
бы некий код отложенной инициализации.
Паттерн заместитель предлагает создать новый класс-дублёр, имеющий
тот же интерфейс, что и оригинальный служебный объект.
При получении запроса от клиента, объект-заместитель сам бы создавал
экземпляр служебного объекта и переадресовывал бы ему всю реальную
работу.
Можно поместить в класс заместителя какую-то промежуточную логику,
которая выполнялась бы до (или после) вызовов этих же методов в
настоящем объекте.
Благодаря одинаковому интерфейсу, объект заместитель можно передать
в любой код, ожидающий сервисный объект.
Пример
Банковский чек — это заместитель пачки наличности. И чек, и наличность имеют
общий интерфейс — ими можно оплачивать товары.

3.

Структура паттерна Заместитель

4.

Функции паттерна
Интерфейс сервиса определяет общий интерфейс
для Сервиса и Заместителя.
Благодаря этому, объект Заместителя можно использовать там,
где ожидается объект Сервиса.
Сервис содержит полезную бизнес-логику.
Заместитель хранит ссылку на объект Сервиса. После того
как Заместитель заканчивает свою работу (например,
инициализацию, логирование, защиту или другое), он передаёт
вызовы вложенному Сервису.
Заместитель может сам отвечать за создание и удаление
объекта Сервиса.
Клиент работает с объектами через Интерфейс сервиса.
Благодаря этому, можно подменить
объект Сервиса объектом Заместителя.

5.

Пример
В этом примере Заместитель помогает добавить
в программу механизм ленивой инициализации и
кеширования тяжёлой служебной библиотеки
интеграции с Youtube.
Оригинальный объект начинал загрузку по сети,
даже если пользователь запрашивал одно и то
же видео.
Заместитель же, загружает видео только один
раз, используя для этого служебный объект, но в
остальных случаях, возвращает кешированный
файл.

6.

Интерфейс удалённого сервиса и
реализация
interface ThirdPartyYoutubeLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
// Конкретная реализация сервиса. Методы этого класса запрашивают у
ютуба
// различную информацию. Скорость запроса зависит от интернет-канала
// пользователя и состояния самого ютуба. Чем больше будет вызовов к
сервису,
// тем менее отзывчивой будет программа.
class ThirdPartyYoutubeClass is
method listVideos() is Send API request to Youtube.
method getVideoInfo(id) is Get a meta information about some video.
method downloadVideo(id) is Download video file from Youtube.

7.

Работа с кэшем
// С другой стороны, можно кешировать запросы к ютубу и не повторять их какое-то
// время, пока кеш не устареет. Но внести этот код напрямую в сервисный класс
// нельзя, так как он находится в сторонней библиотеке. Поэтому мы поместим
// логику кеширования в отдельный класс-обёртку. Он будет делегировать запросы к
// сервисному объекту, только если нужно непосредственно выслать запрос.
class CachedYoutubeClass implements ThirdPartyYoutubeLib is
private field service: ThirdPartyYoutubeClass
private field listCache, videoCache
field needReset
constructor CachedYoutubeClass(service: ThirdPartyYoutubeLib) is
this.service = service
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache
method downloadVideo(id)
is if (!downloadExists(id) || needReset)
service.downloadVideo(id)

8.

Использование прокси класса
/ Класс GUI, который использует сервисный объект. Вместо реального сервиса, мы
// подадим ему объект-заместитель. Клиент ничего не заметит, так как
// заместитель имеет тот же интерфейс, что и сервис.
class YoutubeManager is
protected field service: ThirdPartyYoutubeLib
constructor YoutubeManager(service: ThirdPartyYoutubeLib) is
this.service = service
method renderVideoPage() is
info = service.getVideoInfo()
Render the video page.
method renderListPanel() is
list = service.listVideos()
Render the list of video thumbnails.
method reactOnUserInput() is
renderVideoPage()
renderListPanel()

9.

Клиент прокси -класса
// Конфигурационная часть приложения создаёт и
передаёт клиентам
// объект заместителя.
class Application is
method init() is
youtubeService = new
ThirdPartyYoutubeClass()
youtubeProxy = new
CachedYoutubeClass(youtubeService)
manager = new YoutubeManager(youtubeProxy)
manager.reactOnUserInput()

10.

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

11.

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

12.

Шаги реализации
• Определите интерфейс,
который бы сделал заместитель и оригинальный объект взаимозаменяемыми.
• Создайте класс заместителя.
Он должен содержать ссылку на сервисный объект. В большинстве случаев,
сервисный объект создаётся самим заместителем. В редких случаях, заместитель
получает готовый сервисный объект от клиента через конструктор.
• Реализуйте методы заместителя в зависимости от его предназначения.
В большинстве случаев, проделав какую-то полезную работу, методы
заместителя должны передать запрос сервисному объекту.
• Подумайте о введении фабрики,
которая решала бы какой из объектов создавать — заместитель или реальный
сервисный объект.
Но с другой стороны, эта логика может быть помещена в создающий метод
самого заместителя.
• Подумайте, не реализовать ли вам ленивую инициализацию
сервисного объекта при первом обращении клиента к методам заместителя.

13.

Преимущества и недостатки
• Позволяет контролировать сервисный
объект незаметно для клиента.(+)
• Может работать, даже если сервисный
объект ещё не создан.(+)
• Может контролировать жизненный цикл
служебного объекта.(+)
• Усложняет программу за счёт
дополнительных классов.(-)
• Увеличивает время отклика от сервиса.(-)

14.

Отношения с другими
паттернами
• Адаптер предоставляет классу альтернативный
интерфейс. Заместитель предоставляет тот же
интерфейс. Декоратор предоставляет расширенный
интерфейс.
• Фасад похож на Заместитель тем, что замещает
сложную подсистему и может сам её инициализировать.
Но в отличие от Фасада, Заместитель имеет тот же
интерфейс, что его служебный объект, благодаря чему их
можно взаимозаменять.
• Декоратор и Заместитель имеют похожие структуры, но
разные назначения. Они похожи тем, что оба построены
на композиции и делегировании работы другому объекту.
Паттерны отличаются тем, что Заместитель сам
управляет жизнью сервисного объекта, а
обёртывание Декораторов контролируется клиентом.

15.

Применимость в java
Паттерн Заместитель применяется в Java коде тогда, когда надо заменить настоящий объект его
суррогатом, причём незаметно для клиентов настоящего объекта.
Это позволит выполнить какие-то добавочные функции до или после основного поведения настоящего
объекта.
Примеры Заместителя в стандартных библиотеках Java:
java.lang.reflect.Proxy
java.rmi.*
javax.ejb.EJB Пример:
public class UserServiceProxy extends UserService implements Serializable {
public User find(Long id) {
UserService instance = getAnAvailableInstanceFromPool();
User result = instance.find(id);
releaseInstanceToPool(instance);
return result; }
public Long save(User user) {
UserService instance = getAnAvailableInstanceFromPool();
Long result = instance.save(user);
releaseInstanceToPool(instance);
return result; } // ...
}

16.

Применимость в java
javax.inject.Inject Пример(Context Dependency Injection):
public CDIManagedBeanProxy extends ActualManagedBean
implements Serializable {
public String getSomeProperty() {
ActualManagedBean instance = CDI.resolveItSomehow();
return instance.getSomeProperty(); }
public void setSomeProperty(String someProperty) {
ActualManagedBean instance = CDI.resolveItSomehow();
instance.setSomeProperty(someProperty); } }
javax.persistence.PersistenceContext
Признаки применения паттерна:
Класс заместителя чаще всего делегирует всю настоящую
работу своему реальному объекту. Заместители часто сами
следят за жизненным циклом своего реального объекта.

17.

Еще раз Применимость
Классы-заместители активно применяются
там, где нужно спрятать исходный объект и
добавить к его методам некоторое
поведение:
• позволить отложить создание
дорогостоящего объекта,
• контролировать количество вызовов
метода или
• спрятать удаленную природу объекта

18.

О заместителе
Заместитель — это объект, который выступает
прослойкой между клиентом и реальным
сервисным объектом.
Заместитель получает вызовы от клиента,
выполняет свою функцию (контроль доступа,
кеширование, изменение запроса и прочее), а
затем передаёт вызов сервисному объекту.
Заместитель имеет тот же интерфейс, что и
реальный объект, поэтому для клиента нет
разницы — работать через заместителя или
напрямую.

19.

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

20.

Интерфейс удалённого сервиса
import java.util.HashMap;
public interface ThirdPartyYoutubeLib {
public HashMap<String, Video>
popularVideos();
public Video getVideo(String videoId);
}

21.

Обсуждение паттерна «Заместитель»
«Заместитель» является одним из немногих паттернов
проектирования, который с течением времени претерпел
довольно серьезные изменения. В классическом труде «банды
четырех» описаны три основных сценария использования
паттерна «Заместитель».
• ..Удаленный заместитель отвечает за кодирование запроса и
его аргументов для работы с компонентом в другом адресном
пространстве.
• ..Виртуальный заместитель может кэшировать дополнительную
информацию о реальном компоненте, чтобы отложить его
создание.
• ..Защищающий заместитель проверяет, имеет ли вызывающий
объект необходимые для выполнения запроса права.

22.

Нарушение принципа замещения
Лисков
• удаленный заместитель нарушает принцип
замещения Лисков. При замене локальной
реализации удаленным заместителем
поведение приложения обязательно
изменится.
• Изменения коснутся времени исполнения и
обработки ошибок. Зачастую может
потребоваться полное перепроектирование
клиента с учетом его новой удаленной
природы

23.

Примеры в .NET Framework
..Удаленные классы-заместители.
В коммуникационных технологиях в .NET Framework
применяются удаленные прокси-классы:
ChannelBase<T> в WCF, RealProxy в .NETRemoting.
..Виртуальные классы-заместители в ORM-фреймворках.
В современных ORM (Object-RelationalMapping —
объектно-реляционное отображение), таких как
NHibernate или EntityFramework, применяются
специализированные виртуальные классы-заместители.
ORM генерирует оболочку над сущностями
(dataentities), чтобы отслеживать изменения и
генерировать корректный SQL-код для обновления
записей в базе данных.

24.

Литература
• А. Швец Погружение в ПАТТЕРНЫ
ПРОЕКТИРОВАНИЯ
Тепляков С.
Паттерны проектирования на платформе
.NET. — СПб.: Питер, 2015. — 320 с.: ил.
ISBN 978-5-496-01649-0
English     Русский Правила