Адаптер шаблон проектирования. Pattern Adapter. Помещение квадратных колышков в круглые отверстия

Перед прочтением ознакомьтесь с , в котором описаны принятые соглашения и понятия. Данная статья дополняется с некоторой периодичностью, так что если вы ее читали ранее, не факт что данные не изменились.

Adapter (Адаптер) относиться к классу структурных паттернов. Он используется для преобразования одного интерфейса в другой, необходимый клиенту. Адаптер обеспечивает совместимость несовместимых интерфейсов, реализуя прослойку.

Принцип работы

Адаптер наследует открытым способом целевой интерфейс (назовем его Target ), и закрытым способом адаптируемый интерфейс (Adaptee ). В реализации методов целевого интерфейса происходит перенаправление (делегирование) запросов классу с адаптируемым интерфейсом

Пример

// Целевой интерфейс, клиент умеет работать только с ним interface iTarget { public function query(); } // Адаптируемый интерфейс. Клиент с ним не умеет работать, но очень хочет interface iAdaptee { public function request(); } // Класс, реализующий адаптирумым интерфейс class Adaptee implements iAdaptee { public function request() { return __CLASS__ . "::" . __METHOD__; } } class Adapter implements iTarget { protected $adaptee = null; public function __construct() { $this -> adaptee = new Adaptee(); } public function query() { return $this -> adaptee -> request(); } } $Target = new Adapter(); print $Target -> query(); // "Adaptee::request"

Заключение

Адаптер может адаптировать в единый сразу несколько интерфейсов, такой паттерн называют адаптер объектов Использование этого паттерна оправдано в нескольких случаях. Если вы хотите использовать существующий класс с иным интерфейсом. Если адаптируемый интерфейс собираетесь использовать в нескольких местах, и не имеете возможности везде привести к единому виду, тогда использование сменных адаптеров может оказаться хорошей идеей.

    Адаптер (шаблон проектирования)/Примеры кода - Основная статья: Адаптер (шаблон проектирования) Пример реализации шаблона на C# using System; namespace Adapter { class MainApp { static void Main() { … Википедия

    Шаблон Proxy (шаблон проектирования)

    Шаблон проектирования - У этого термина существуют и другие значения, см. Паттерн. В разработке программного обеспечения, шаблон проектирования или паттерн (англ. design pattern) повторимая архитектурная конструкция, представляющая собой решение проблемы… … Википедия

    Интерфейс (шаблон проектирования) - Шаблон проектирования Интерфейс Interface Описан в Design Patterns Нет В информатике, шаблон интерфейса не является особым шаблоном среди шаблонов проектирования. Он является общим методом для структурирования компьютерных программ для того … Википедия

    Заместитель (шаблон проектирования) - Шаблон Proxy (Заместитель) Шаблон проектирования. Предоставляет объект, контролирующий доступ, перехватывая все вызовы к нему. Содержание 1 Цель 1.1 Проблема 1.2 Решение 2 Плюсы 3 … Википедия

    Хранитель (шаблон проектирования) - Шаблон проектирования Хранитель Memento Тип: поведенческий Описан в Design Patterns Да Хранитель (также известный как Memento, Token, Лексема) поведенческий шаблон проектирования. Позволяет, не нарушая инкапсуляцию, зафикс … Википедия

    Итератор (шаблон проектирования) - Шаблон проектирования Итератор Iterator Тип: поведенческий Описан в Design Patterns Да Шаблон Iterator (также известный как Cursor) Шаблон проектирования, относится к паттернам поведения. Представляет собой объект, позволяющий получить … Википедия

    Интерпретатор (шаблон проектирования) - Шаблон проектирования Интерпретатор Interpreter Тип: поведенческий Назначение: решает часто встречающуюся, подверженную изменениям задачу Описан в Design Patterns Да Шаблон Интерпретатор (англ. … Википедия

    Компоновщик (шаблон проектирования) - Шаблон проектирования Компоновщик Composite Тип: структурный Описан в Design Patterns Да Компоновщик (англ. Composite pattern) шаблон проектирования, относится к структурным паттернам, объединяет объек … Википедия

    Состояние (шаблон проектирования) - Шаблон проектирования Состояние State Тип: поведенческий Описан в Design Patterns Да Состояние (англ. State) шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект … Википедия

Адаптер (Adapter / Wrapper).

Тип

Структурный шаблон проектирования (Structural).

Описание

Шаблон Адаптер предназначен для приведения интерфейса объекта к требуемому виду.

Данный шаблон применяется если:

  • существующий объект, называемый адаптируемым, предоставляет необходимые функции, но не поддерживает нужного интерфейса;
  • (или) неизвестно заранее, с каким интерфейсами придется работать адаптируемому объекту;
  • (или) формат входных или выходных данных метода не совпадает с требуемым.

Главная задача Адаптера – реализация требуемого интерфейса и трансляция его вызовов адаптируемому объекту. Подобную ситуацию можно встретить при использовании сторонних библиотек. Они далеко не всегда предоставляют интерфейсы, которые необходимы в для связи с другими объектами. При этом изменить код или добавить поддержку интерфейса не предоставляется возможным.

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

Еще один пример использования шаблона – адаптация поведения. Такой подход используется, например, в.NET при работе с COM объектами: полученные от них коды ошибок из значений типа HRESULT преобразуется в выбросы исключений типа COMExceptions .

Таким образом, цель Адаптера – предоставить возможность повторного использования существующего кода, независимо от отличий в интерфейсе или поведении. Кроме того, меняя адаптируемые объекты возможно влиять на функции, выполняемые в программе.

Различают четыре роли, отводимые участвующим в работе шаблона объектам:

  1. Адаптируемый объект (Adaptee);
  2. Цель (Target), определяющая требуемый интерфейс;
  3. Адаптер (Adapter);
  4. Клиент (Client), который умеет работать с только объектами, реализующими интерфейс цели.

Стоит принять во внимание следующие моменты:

  1. Адаптер не обязательно должен содержать только вызовы адаптируемого объекта. Он может заниматься обработкой входных и выходных данных, приводя их к нужному формату.
  2. В рамках шаблона можно использовать несколько адаптируемых объектов для реализации заданного интерфейса.
  3. Реализация шаблона может самостоятельно дополнить необходимую функциональность, если такой нет у адаптируемого объекта.
  4. Адаптер может работать не только с заданным адаптируемым объектом, но и его наследниками.
  5. Возможно замещение части методов адаптируемого объекта. В этом случае от него необходимо создать подкласс, в котором произвести нужные замещения. И уже результат использовать в Адаптере.
  6. Реализация адаптера может быть двухсторонней. В этом случае адаптируемый объект реализует промежуточную логику для связи двух целей, каждая из которых требует наличие своего интерфейса.
  7. Можно использовать Отложенную инициализацию для создания экземпляра адаптируемого объекта.

Схожие шаблоны и их отличия

Адаптер Изменяет интерфейс объекта не изменяя его функциональности. Может адаптировать несколько объектов к одному интерфейсу. Позволяет повторно использовать уже существующий код. Содержит или наследует адаптируемый объект.
Фасад Объединяет группу объектов под одним специализированным интерфейсом. Упрощает работу с группой объектов, вносит новый уровень абстракции. Содержит или ссылается на объекты, необходимые для реализации специализированного интерфейса.
Мост Разделяет объект на абстракцию и реализацию. Используется для иерархии объектов. Позволяет отдельно изменять (наследовать) абстракцию и реализацию, повышая гибкость системы. Содержит объект(реализацию), который предоставляет методы для заданной абстракций и ее уточнений (наследников).
Декоратор Расширяет возможности объекта, изменяет его поведение. Поддерживает интерфейс декорируемого объекта, но может добавлять новые методы и свойства. Дает возможность динамически менять функциональность объекта. Является альтернативой наследованию (в том числе множественному). Содержит декорируемый объект. Возможна цепочка объектов, вызываемых последовательно.
Прокси Прозрачно замещает объект и управляет доступом к нему. Не изменяет интерфейс или поведение. Упрощает и оптимизирует работу с объектом. Может добавлять свою функциональность, скрывая ее от клиента. Содержит объект или ссылку на него, может управлять существованием замещенного объекта.
Компоновщик Предоставляет единый интерфейс для взаимодействия с составными объектами и их частями. Упрощает работу клиента, позволяет легко добавлять новые варианты составных объектов и их частей. Включается в виде интерфейса в составные объекты и их части.
Приспособленец

Не ставит целью изменение интерфейса объекта. Но это может потребоваться для получения обратно данных из вынесенной части состояния.

Позволяет уменьшить число экземпляров объекта в приложении и тем самым сэкономить его ресурсы. Выносит контекстно-зависимую часть состояния объекта вовне, заменяя несколько его экземпляров одним.

Реализация шаблона в общем виде

По схеме, используемой для работы с адаптируемым объектом, выделяют два варианта:

  1. Адаптер объекта – использует композицию, т.е. содержит экземпляр адаптируемого объекта.
  2. Адаптер класса – использует наследование от адаптируемого объекта для получения его функциональности.

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

Но встречаются ситуации, когда требуется применение адаптера класса. Например, необходимость доступа к protected методам. В другом случае может потребоваться использовать Адаптер и вместо адаптируемого объекта.

Реализация Адаптера объекта

  • создаем класс ITarget или являться наследником от класса Target с нужным интерфейсом;
  • в его закрытом поле _adaptee размещаем экземпляр адаптируемого объекта;
  • реализуем интерфейс ITarget

Реализация Адаптера класса

  • создаем класс , который будет реализовывать требуемый интерфейс ITarget;
    • шаблон позволяет использовать наследование от класса Target , но в C# этот вариант не может быть реализован. Причина – запрет на множественное наследование.
  • добавлением классу наследование от адаптируемого класса;
  • реализуем интерфейс ITarget , в методах которого вызываем нужные методы адаптируемого объекта;
  • клиент использует экземпляр класса и получает требуемую функциональность.

Примеры реализации

1. Адаптер объекта

Задача: в приложении необходимо воспроизводить звуковые сигналы для оповещения пользователя, которые в данный момент хранятся в формате.wav.

Интерфейс IAudioPlayer содержит описание функций самого простого медиаплеера, осуществляющего загрузку и воспроизведение аудиофайла.

///

Loads the audio file. void Load(string file); /// Plays the audio file. void Play(); }

При реализации обратим внимание, что.NET уже содержит класс SoundPlayer , предоставляющий подобные возможности. Однако данный класс не поддерживает заданный интерфейс. Поэтому воспользуемся шаблоном Адаптер:

///

Simple audio player interface. public class SoundPlayerAdapter: IAudioPlayer { /// Adaptee object. private readonly Lazy _lazyPlayer = new Lazy(); /// Loads the audio file. public void Load(string file) { this._lazyPlayer.Value.SoundLocation = file; this._lazyPlayer.Value.Load(); } /// Plays the audio file. public void Play() { this._lazyPlayer.Value.Play(); } }

Код очень простой. Можно отметить только использование Отложенной инициализации (с помощью generic-класса Lazy ) для адаптируемого класса. Это сделано на случай, если не будет загружено или воспроизведено ни одного аудиофайла.

Теперь возможно использовать возможности SoundPlayer в разрабатываемом приложении:

Private IAudioPlayer _player = new SoundPlayerAdapter(); public void NotifyUser(int messageCode) { string wavFile = string.Empty; /* Skipped */ // play the audio file if (!string.IsNullOrEmpty(wavFile)) { this._player.Load(wavFile); this._player.Play(); } }

Может возникнуть вопрос: почему сразу не использовать класс SoundPlayer ? В отличии от явного использования указанного класса, данный подход обеспечил независимость кода от конкретной реализации медиаплеера. Например, в дальнейшем можно легко заменить SoundPlayerAdapter на другой класс для поддержки файлов другого формата.

2. Адаптер класса

Давайте рассмотрим решение той же задачи с помощью адаптера класса.

В этот раз необходимо наследовать SoundPlayerAdapter не только от интерфейса IAudioPlayer , но и от класса SoundPlayer . В результате получаем готовый метод Play() , а вот метод Load() придется определить самостоятельно.

///

Simple audio player interface. public class SoundPlayerAdapter: SoundPlayer, IAudioPlayer { /// Loads the audio file. public void Load(string file) { this.SoundLocation = file; this.Load(); } }

Что изменилось по сравнению с первым вариантом?

Исчезла отложенная инициализация адаптируемого объекта, которая в первом варианте была "из коробки" и прозрачна для клиентского кода.

Код реализации стал короче, т.к. пришлось создавать только недостающие методы. Возможна ситуация, когда класс Адаптера не содержал бы никакого кода. Например:

///

Simple audio player interface. public interface IAudioPlayer { /// Gets or sets the file path or URL of the .wav file to load. string SoundLocation { get; set; } /// Loads the audio file. void Load(); /// Plays the audio file. void Play(); } /// Simple audio player interface. public class SoundPlayerAdapter2: SoundPlayer, IAudioPlayer {}

Кроме того, экземпляр класса SoundPlayerAdapter теперь предоставляет полный перечень свойств и методов, унаследованных от адаптируемого объекта . Он может быть использован вместо экземпляра SoundPlayer при необходимости. Но стоит помнить, что в этом случае усиливается связь между адаптируемым объектом и кодом приложения. И это самый большой минус данного варианта.

Вернемся к рассмотрению структурных паттернов проектирования. На этот раз мы рассмотрим шаблон проектирования под названием Adapter (его еще называют Wrapper на ряду с паттерном Facade).

В этой статье поговорим о следующем:

Итак, паттерн Adapter используется для того, чтобы объекты с одним интерфейсом (контрактом) мог работать там, где необходим объект с совершенно другим интерфейсом. Существует два типа адаптеров - Class Adapter и Object Adapter.

Для начала мы рассмотрим каждый из этих типов, а потом я объясню разницу между двумя wrapper"ами - адаптером и фасадом.

Object Adapter

Object Adapter достигает своей цели с помощью композиции. На диаграмме, представленной ниже, клиенту требуется использовать интерфейс TargetInterface. Для этого создается класс ObjectAdapter, который реализует интерфейс TargetInterface, а также хранит объект класса Adaptee. При вызове метода targetMethod у Адаптера, осуществляется вызов соответствующего метода у адаптируемого интерфейса.

В самом простом случае реализация ObjectAdapter будет такой:

Public class ObjectAdapter implements TargetInterface { private Adaptee adaptee; public void targetMethod() { adaptee.method() } }

Плюс такого подхода в том, что мы полностью отделяем клиентский интерфейс от адаптируемого интерфейса.

Class Adapter

В случае с Class Adapter"ом, для достижения нашей цели используется множественное наследование. Наш ClassAdapter наследуется от клиентского интерфейса и от Адаптируемого интерфейса. Так как в Java нет множественного наследования, то только один из предков может быть абстрактным/конкретным классом. Второй предок будет интерфейсом, что не всегда удобно.

Диаграмма классов:

А вот и тривиальная реализация класса ClassAdapter:

Public class ClassAdapter extends Adaptee implements TargetInterface { public void targetMethod() { method(); } }

Хочу обратить ваше внимание, что при такой реализации адаптера может возникнуть конфликт сигратур методов. Такой проблемы у Object Adapter нет.

Class Adapter считается более простым решением в случае когда не требуется жесткого разделения клиентского и адаптируемого интерфейсов.

Разница между Адаптером и Фасадом

Теперь хочу сказать несколько слов по поводу паттерна Фасад, который как и Адаптер является Wrapper"ом. Facade определяет новый интерфейс, в то время как Адаптер использует существующие интерфейсы.

Не стоит сравнивать Фасад и Адаптер так: мол, Фасад может оборачивать несколько классов, а Адаптер адаптирует только один. Очень может быть, что Адаптер понадобится для адаптации нескольких классов и наоборот, Фасад придется использовать для упрощения всего лишь одного комплексного класса. Так что разница этих двух паттернов не в количестве оборачиваемых сущностей, а в том, для чего они это делают.

Пример использования Адаптера в JDK

В стандартной библиотеке тоже можно встретить примеры использования Адаптера. Наверное, самый популярный вариант использования - это java.io.InputStreamReader и OutputStreamWriter.

Конструктор InputStreamReader принимает на вход InputStream и в результате адаптирует поток в Reader.

Позже выложу код использования Адаптеров из реальных проектов, в которых я принимал участие, а пока жду ваших вопросов и комментариев. Удачи.

Паттерн Адаптер, Обертка (Adapter, Wrapper)

Название и классификация паттерна

Адаптер - паттерн, структурирующий как классы, так и объекты.

Назначение паттерна Adapter

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

Паттерн Adapter, представляющий собой программную обертку над существующими классами, преобразует их интерфейсы к виду, пригодному для последующего использования.

Рассмотрим простейший пример применения паттерна Adapter. Пусть мы разрабатываем систему климат-контроля. Важным компонентом такой системы является температурный датчик, для которого уже имеется готовое программное обеспечение, представляющее собой некоторый класс с соответствующим интерфейсом для работы со шкалой Фаренгейта. Но нас интересует температура по шкале Цельсия. Задача может быть легко решена с помощью паттерна Adapter.

Рассмотрим другой пример - графический редактор, в котором пользователи могут рисовать на экране графические элементы (линии, многоугольники, текст и т. д.) и организовывать их в виде картинок и диаграмм. Основной абстракцией графического редактора является графический объект, который имеет изменяемую форму и изображает сам себя. Интерфейс графических объектов определен абстрактным классом Shape. Редактор определяет подкласс класса Shape для каждого вида графических объектов: LineShape для прямых, PolygonShape для многоугольников и т. д.

Классы для элементарных геометрических фигур, например LineShape и PolygonShape, реализовать сравнительно просто, поскольку заложенные в них возможности рисования и редактирования крайне ограниченны. Но подкласс Text Shape, умеющий отображать и редактировать текст, уже значительно сложнее. В то же время существуют готовые библиотеки для разработки пользовательских интерфейсов, предоставляющие развитый класс Text View для отображения и редактирования текста.

Можно было бы так изменить интерфейс класса Text View, чтобы он соответствовал интерфейсу Shape, только для этого нужен исходный код. Но даже если он доступен, то вряд ли разумно изменять Text View - библиотека не должна приспосабливаться к интерфейсам каждого конкретного приложения.

Вместо этого можно так определить класс Text Shape, что он будет адаптировать интерфейс Text View к интерфейсу Shape. Это допустимо сделать двумя способами: наследуя интерфейс от Shape, а реализацию от Text View; включив экземпляр Text View в Text Shape и реализовав Text Shape в терминах интерфейса Text View. Два данных подхода соответствуют вариантам паттерна Адаптер в его классовой и объектной ипостасях. Класс Text Shape (рис. 40) мы будем называть Адаптером.

На диаграмме показан Адаптер объекта. Видно, как запрос BoundingBox, объявленный в классе Shape, преобразуется в запрос Get Extent, определенный в классе Text View. Поскольку класс Text Shape адаптирует Text View к интерфейсу Shape, графический редактор может воспользоваться классом TextView, хотя тот и имеет несовместимый интерфейс.

Часто Адаптер отвечает за функциональность, которую не может предоставить адаптируемый класс. На рис. 40 показано, как Адаптер

return newTextManipulator^^

Рис. 40. UML-диаграмма Адаптера объекта

выполняет такого рода функции. У пользователя должна быть возможность перемещать любой объект класса Shape в другое место, но в классе TextView такая операция не предусмотрена. Text Shape может добавить недостающую функциональность, самостоятельно реализовав операцию CreateManipulator класса Shape, которая возвращает экземпляр подходящего подкласса Manipulator.

Manipulator - это абстрактный класс объектов, которым известно, как анимировать Shape в ответ на такие действия пользователя, как перетаскивание фигуры в другое место. У класса Manipulator имеются подклассы для различных фигур. Например, TextManipulator - подкласс для Text Shape. Возвращая экземпляр TextManipulator, объект класса TextShape добавляет новую функциональность, которой в классе TextView нет, а классу Shape требуется.

Применимость

Применяйте паттерн Адаптер, когда:

  • хотите использовать существующий класс, но его интерфейс не соответствует вашим потребностям;
  • собираетесь создать повторно используемый класс, который должен взаимодействовать с заранее неизвестными или не связанными с ним классами, имеющими несовместимые интерфейсы;
  • (только для Адаптера объектов!) нужно использовать несколько существующих подклассов, но непрактично адаптировать их интерфейсы путем порождения новых подклассов от каждого. В этом случае Адаптер объектов может приспосабливать интерфейс их общего родительского класса.

Описание паттерна Adapter

Адаптер класса использует множественное наследование для адаптации одного интерфейса к другому.

Пусть класс, интерфейс которого нужно адаптировать к нужному виду, имеет имя Adapt?e. Для решения задачи преобразования его интерфейса паттерн Adapter вводит следующую иерархию классов:

  • виртуальный базовый класс Target. Здесь объявляется пользовательский интерфейс подходящего вида. Только этот интерфейс доступен для пользователя;
  • производный класс Adapter , реализующий интерфейс Target. В этом классе также имеется указатель или ссылка на экземпляр Adapt?e. Паттерн Adapter использует этот указатель для перенаправления клиентских вызовов в Adapt?e. Так как интерфейсы Adapt?e и Target несовместимы между собой, то эти вызовы обычно требуют преобразования.

Структура

Структура паттерна Адаптер показана на рис. 41.

Адаптер объекта применяет композицию объектов.


adaptee->specificRequest()

Рис. 41. UML-диаграмма классов паттерна Adapter

Участники

Target (Shape) - целевой: определяет зависящий от предметной области интерфейс, которым пользуется Client.

Client (DrawingEditor) - клиент: вступает во взаимоотношения с объектами, удовлетворяющими интерфейсу Target.

Adapt?e (Textview) - адаптируемый: определяет существующий интерфейс, который нуждается в адаптации.

Adapter (Text Shape) - Адаптер: адаптирует интерфейс Adapt?e к интерфейсу Target.

Отношения

Клиенты вызывают операции экземпляра Адаптера Adapter. В свою очередь, Адаптер вызывает операции адаптируемого объекта или класса Adapt?e, который и выполняет запрос.

Результаты

Результаты применения Адаптеров объектов и классов различны.

Адаптер класса:

  • адаптирует Adapt?e к Target, перепоручая действия конкретному классу Adapt?e. Поэтому данный паттерн не будет работать, если мы захотим одновременно адаптировать класс и его подклассы;
  • позволяет Адаптеру Adapter заместить некоторые операции адаптируемого класса Adapt?e, так как Adapter есть не что иное, как подкласс Adapt?e;
  • вводит только один новый объект. Чтобы добраться до адаптируемого класса, не нужно никакого дополнительного обращения по указателю.

Адаптер объектов:

  • позволяет одному Адаптеру Adapter работать со многим адаптируемыми объектами Adapt?e, т. е. с самим Adapt?e и его подклассами (если таковые имеются). Адаптер может добавить новую функциональность сразу всем адаптируемым объектам;
  • затрудняет замещение операций класса Adapt?e. Для этого потребуется породить от Adapt?e подкласс и заставить Adapter ссылаться на этот подкласс, а не на сам Adapt?e.

Адаптеры сильно отличаются по тому объему работы, который необходим для адаптации интерфейса Adapt?e к интерфейсу Target. Это может быть как простейшее преобразование, например изменение имен операций, так и поддержка совершенно другого набора операций. Объем работы зависит от того, насколько сильно отличаются друг от друга интерфейсы целевого и адаптируемого классов.

На первом шаге реализации необходимо найти «узкий» интерфейс для Adapt?e, т. е. наименьшее подмножество операций, позволяющее выполнить адаптацию. «Узкий» интерфейс, состоящий всего из пары итераций, легче адаптировать, чем интерфейс из нескольких десятков операций.

«Узкий» интерфейс можно реализовать разными способами.

  • 1. Использование абстрактных операций. Подклассы должны реализовывать эти абстрактные операции и адаптировать иерархически структурированный объект.
  • 2. Использование объектов-уполномоченных. При таком подходе запросы на доступ к иерархической структуре переадресуются объек-ту-уполномоченному.

Классическая реализация паттерна Adapter

Приведем реализацию паттерна Adapter. Для примера выше адаптируем показания температурного датчика системы климат-контроля, переведя их из градусов Фаренгейта в градусы Цельсия (предполагается, что код этого датчика недоступен для модификации).

// Уже существующий класс температурного датчика окружающей среды

class FahrenheitSensor

// Получить показания температуры в градусах Фаренгейта float getFahrenheitTemp() { float t = 32.0;

//... какой-то код return t;

virtual ~Sensor() {}

class Adapter: public Sensor

Adapter(FahrenheitSensor* p): p_fsensor(p) {

Adapted) { delete p_fsensor;

float getTemperature() { return (p_fsensor->getFahrenheitTemp()-32.0)*5.0/9.0;

FahrenheitSensor* p_fsensor;

Sensor* p = new Adapter(new FahrenheitSensor);

cout getTemperature()

Реализация паттерна Adapter на основе закрытого наследования

Пусть наш температурный датчик системы климат-контроля поддерживает функцию юстировки для получения более точных показаний. Эта функция не является обязательной для использования, возможно, поэтому соответствующий метод adjust() объявлен разработчиками защищенным в существующем классе FahrenheitSensor.

Разрабатываемая нами система должна поддерживать настройку измерений. Так как доступ к защищенному методу через указатель или ссылку запрещен, то классическая реализация паттерна Adapter здесь уже не подходит. Единственное решение - наследовать от класса FahrenheitSensor. Интерфейс этого класса должен оставаться недоступным пользователю, поэтому наследование должно быть закрытым.

Цели, преследуемые при использовании открытого и закрытого наследования, различны. Если открытое наследование применяется для наследования интерфейса и реализации, то закрытое наследование - только для наследования реализации.

class FahrenheitSensor

float getFahrenheitTempO { float t = 32.0;

void adjustQ {} // Настройка датчика (защищенный метод)

virtual ~Sensor() {}

virtual float getTemperature() = 0;

virtual void adjust() = 0;

class Adapter: public Sensor, private FahrenheitSensor

Adapter() {} float getTemperature() { return (getFahrenheitTemp()-32.0)*5.0/9.0;

FahrenheitSensor::adjust();

Sensor * p = new Adapter(); p->adjust();

cout getTemperature()

Результаты применения паттерна Adapter

Достоинства паттерна Adapter

Паттерн Adapter позволяет повторно использовать уже имеющийся код, адаптируя его несовместимый интерфейс к виду, пригодному для использования.

Недостатки паттерна Adapter

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

Родственные паттерны

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

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

Заместитель определяет представителя или суррогат другого объекта, но не изменяет его интерфейс.