CodeLAB
на главную карта сайта обратная связь
каталог | задачи | паттерны | исходники | стат | форумы | ссылки
 гость
искать в
Главная >> Каталог задач >> Паттерны >> Структурные >> Адаптер (Adapter)

<< назад
распечатать обсудить >>


Адаптер (Adapter)
реализации: java, количество: 4

Aвтор: this
Дата: 01.07.2007
Просмотров: 54782
Рейтинг: 0/0,0(0)
+
реализации(исходники) +добавить

Имя

«Паттерн
Adapter (или Wrapper)» codelab.ru источник codelab.ru оригинал
Адаптер – паттерн, унифицирующий классы и объекты. codelab.ru источник оригинал codelab.ru

Условия, Задача, Назначение

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

Мотивация

Обычно класс из инструментальной библиотеки, спроектированный для повторного использования, не удается сходу взять и использовать потому, что его интерфейс естественно не соответствует тому, который нужен конкретному приложению. codelab.ru codelab.ru источник оригинал

Рассмотрим, например, графический редактор, благодаря которому пользователи могут рисовать на экране графические элементы (линии, многоугольники, текст и т.д.) и организовывать их в виде картинок и диаграмм. Основной абстракцией графического редактора является графический элемент, который имеет изменяемую форму и изображает сам себя. Интерфейс графических элементов определен абстрактным классом Shape. Редактор определяет подкласс класса Shape для каждого вида графических объектов: LineShape для прямых, PolygonShape для многоугольников и т.д. оригинал codelab.ru codelab.ru источник
Классы для элементарных геометрических фигур, например LineShape и PolygonShape, реализовать сравнительно просто, поскольку заложенные в них возможности рисования и редактирования крайне ограничены. Но подкласс TextShape, умеющий отображать и редактировать текст, уже значительно сложнее, поскольку даже для простейших операций редактирования текста нужно нетривиальным образом обновлять экран и управлять буферами. В то же время, возможно, существует уже готовая библиотека для разработки пользовательских интерфейсов, которая предоставляет развитый класс TextView, позволяющий отображать и редактировать текст. В идеале мы хотели бы повторно использовать TextView для реализации TextShape, но библиотека  разрабатывалась без учета классов Shape, поэтому заставить объекты TextView и Shape работать совместно не удается. оригинал codelab.ru codelab.ru источник
Так каким же образом существующие и независимо разработанные классы вроде TextView могут работать в приложении, которое спроектировано под другой,  несовместимый интерфейс? Можно было бы так изменить интерфейс класса TextView, чтобы он соответствовал интерфейсу Shape, только для этого нужен исходный код. Но даже если он доступен, то вряд ли разумно изменять TextView: библиотека не должна приспосабливаться к интерфейсам каждого конкретного приложения. источник оригинал codelab.ru codelab.ru
Вместо этого мы могли бы определить класс TextShape так, что он будет источник codelab.ru codelab.ru оригинал
адаптировать интерфейс класса TextView к интерфейсу Shape. Это допустимо сделать codelab.ru источник оригинал codelab.ru
двумя способами: источник codelab.ru codelab.ru оригинал
  1. Реализуя интерфейс Shape, и наследуясь от Text View источник codelab.ru оригинал codelab.ru
  2. Включив экземпляр TextView в TextShape и реализовав TextShape (т.е. рализовав интерфейс Shape) используя интерфейс класса TextView, т.е. попросту пользуясь классом TextView. codelab.ru источник оригинал codelab.ru

2 данных подхода соответствуют вариантам паттерна адаптер в его классовой и объектной ипостасях. Класс TextShape мы будем называть адаптером. codelab.ru оригинал источник codelab.ru

codelab.ru codelab.ru оригинал источник

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

Признаки применения, использования паттерна Адаптер (Adapter)

Если: codelab.ru codelab.ru источник оригинал
  1. Хотите использовать существующий класс, но его интерфейс не соответствует вашим потребностям оригинал codelab.ru источник codelab.ru
  2. Собираетесь создать повторно используемый класс, который должен взаимодействовать с заранее неизвестными или не связанными с ним классами, имеющими несовместимые интерфейсы источник codelab.ru codelab.ru оригинал
  3. (Только для адаптера объектов!) Нужно объединить интерфейсы нескольких классов, причем в наличии могут быть уже только объекты-подклассы каждого из этих классов, а не экземпляры самих этих классов. В этом случае адаптер объектов может приспосабливать интерфейс их общего родительского класса. codelab.ru codelab.ru оригинал источник

Решение

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

codelab.ru оригинал codelab.ru источник

Адаптер объекта применяет композицию объектов. codelab.ru источник codelab.ru оригинал

источник codelab.ru codelab.ru оригинал

Участники паттерна Адаптер (Adapter)

источник оригинал codelab.ru codelab.ru

  1. Target (Shape) – целевой интерфейс.
    Определяет зависящий от предметной области интерфейс, которым пользуется Client. codelab.ru оригинал источник codelab.ru
  2. Client (DrawingEditor) – клиент.
    Взаимодействует с объектами, удовлетворяющими интерфейсу Target. codelab.ru codelab.ru источник оригинал
  3. Adaptee (TextView) – адаптируемый интерфейс.
    Определяет существующий интерфейс класса (библиотеки), который нуждается в адаптации. codelab.ru оригинал источник codelab.ru
  4. Adapter (Text Shape) – адаптер.
    Класс, адаптирующий интерфейс Adaptee к интерфейсу Target. codelab.ru источник оригинал codelab.ru

Схема использования паттерна Адаптер (Adapter)

Клиенты (Client) вызывают операции экземпляра адаптера Adapter. В свою очередь источник codelab.ru оригинал codelab.ru

адаптер вызывает операции адаптируемого объекта или класса Adaptee, который и выполняет запрос. codelab.ru оригинал codelab.ru источник

Вопросы, касающиеся реализации паттерна Адаптер (Adapter)

Ниже приведены вопросы, которые следует рассмотреть, когда вы решаете источник оригинал codelab.ru codelab.ru
применить паттерн адаптер: оригинал codelab.ru codelab.ru источник
  1. Объем работы по адаптации.
    Адаптеры сильно отличаются по тому объему работы, который необходим для адаптации интерфейса Adaptее к интерфейсу Target. Это может быть как простейшее преобразование, например, изменение имен операций, так и поддержка совершенно другого набора операций. Объем работы зависит от того, насколько сильно отличаются друг от друга интерфейсы целевого и адаптируемого классов; оригинал codelab.ru codelab.ru источник
  2. Сменные адаптеры.
    Степень повторной используемости класса тем выше, чем меньше предположений делается о тех классах, которые будут его применять. Реализуя адаптацию интерфейса Adaptee через дополнительный класс (Adapter), вы отказываетесь от предположения, что другим классам станет доступен тот же самый интерфейс Adaptee. Другими словами, адаптация интерфейса позволяет включить ваш класс(Adaptee) в существующие системы(Client), которые спроектированы для класса с другим интерфейсом(Target). Помимо этого, существует специальное понятие сменный адаптер (pluggable adapter), обозначающее классы со встроенной адаптацией интерфейса.
    Рассмотрим виджет TreeDisplay, позволяющий графически отображать древовидные структуры. Если бы это был специализированный виджет, предназначенный только для одного приложения, то мы могли бы потребовать специального интерфейса от объектов, которые он отображает. Но если мы хотим сделать его повторно используемым (например, частью библиотеки полезных виджетов), то предъявлять такое требование неразумно. Разные приложения, скорей всего, будут определять собственные классы для представления древовидных структур, и не следует заставлять их пользоваться именно нашим интерфейсом абстрактного класса Tree. А у разных структур деревьев будут и разные интерфейсы.
    Например, в иерархии каталогов добраться до потомков удастся с помощью операции GetSubdirectories, тогда как для другой иерархии наследования соответствующая операция может называться GetSubclasses или GetInnerElements например. Повторно используемый виджет TreeDisplay должен «уметь» отображать иерархии обоих видов, даже если у них разные интерфейсы. Другими словами, в TreeDisplay должна быть встроена возможность адаптации интерфейсов. О способах встраивания адаптации интерфейсов в классы будет сказано ниже. оригинал codelab.ru codelab.ru источник
  3. Использование двусторонних адаптеров для обеспечения прозрачности.
    Адаптеры объектов непрозрачны для всех клиентов. Адаптированный объект уже не обладает интерфейсом Adaptее, так что его нельзя использовать там, где Adaptee был применим. Двусторонние адаптеры классов способны обеспечить такую прозрачность. Точнее, они полезны в тех случаях, когда клиент должен видеть объект по-разному.
    Для примера можно привести двусторонний адаптер, который интегрирует каркас графических редакторов Unidraw и библиотеку для разрешения ограничений QOCA. В обеих системах – есть классы, явно представляющие переменные: в Unidraw это StateVariable, а в QOCA –ConstraintVariable. Чтобы заставить Unidraw работать совместно с QOCA, ConstraintVariable нужно адаптировать к StateVariable. А для того чтобы решения QOCA распространялись на Unidraw, StateVariable следует адаптировать к ConstraintVariable.

    Здесь применен двусторонний адаптер класса ConstraintStateVariable, который является подклассом одновременно StateVariable и ConstraintVariable и адаптирует оба интерфейса друг к другу. Множественное наследование в данном случае вполне приемлемо, поскольку интерфейсы адаптированных классов существенно различаются. Двусторонний адаптер класса соответствует интерфейсам каждого из адаптируемых классов и может работать в любой системе, т.е. и в QOCA и в Unidraw в данном случае. источник оригинал codelab.ru codelab.ru
Рассмотрим 3 способа реализации сменных адаптеров для описанного выше виджета TreeDisplay, который может автоматически отображать иерархические структуры. Первый шаг, общий для всех трех реализаций, - найти «узкий» интерфейс для Adaptee, то есть наименьшее подмножество операций, позволяющее выполнить адаптацию. «Узкий» интерфейс, состоящий всего из пары итераций, легче адаптировать, чем интерфейс из нескольких десятков операций. Для TreeDisplay адаптации подлежит любая иерархическая структура. Минимальный интерфейс мог бы включать всего две операции: codelab.ru источник codelab.ru оригинал
  1. Определение графическое представление узла в иерархической структуре. codelab.ru оригинал codelab.ru источник
  2. Доступ к потомкам узла. codelab.ru codelab.ru оригинал источник
«Узкий» интерфейс приводит к трем подходам к реализации: codelab.ru оригинал источник codelab.ru
  1. Использование абстрактных операций.
    Определим в классе TreeDisplay абстрактные операции, которые соответствуют «узкому» интерфейсу класса Adaptee. И для каждого нового вида иерархичной структуры мы будем создавать по подклассу, который будет инкапсулировать объект своей конкретной структуру. Далее подкласс реализует эти абстрактные операции, пользуясь своим внутренним классом, и таким образом адаптирует иерархически структурированный объект. Например, подкласс DirectoryTreeDisplay, например, при их реализации будет осуществлять доступ к структуре каталогов файловой системы. А подкласс ManagerTreeDisplay специализирует узкий интерфейс таким образом, чтобы он мог отображать иерархию управленцев, составленную из объектов Manager.

    Таким образом, получаем довольно распространенную картину: в интерфейсе родительского класса имеется несколько контекстно-зависимых переопределяемых операций (методов), которые являются ключевыми и инкапсулируют в себе все различия, появляющиеся при смене одной структуры другой. Другие операции класса, используя эти ключевые методы, являются уже одинаковыми для всех структур (контекстно-независимыми), реализующие одинаковые алгоритмы во всех ситуациях. Поэтому достаточно лишь создать подкласс и переопределить в нем эти несколько ключевых операций, чтобы получить класс, полностью функционирующий с другой структурой, в другом контексте. codelab.ru источник оригинал codelab.ru

    codelab.ru codelab.ru оригинал источник
  2. Использование объектов-уполномоченных.
    При таком подходе TreeDisplay инкапсулирует в себе объект уполномоченного и переадресует ему запросы на доступ к иерархической структуре. Таким образом TreeDisplay может реализовывать различные стратегии адаптации, подставляя разных уполномоченных.
    Например, предположим, что существует класс DirectoryBrowser, который использует TreeDisplay. DirectoryBrowser сам по себе может быть еще и уполномоченным для адаптации TreeDisplay к иерархической структуре каталогов.

    codelab.ru codelab.ru источник оригинал

    В статически типизированных языках вроде Java, C++, C# для этого потребуется явно реализовать интерфейс уполномоченного. Специфицировать такой интерфейс можно, поместив «узкий» интерфейс, который необходим классу TreeDisplay, в абстрактный класс TreeAccessorDelegate. После этого допустимо добавить этот интерфейс к выбранному уполномоченному - в данном случае DirectoryBrowser - с помощью наследования.
    Если у DirectoryBrowser еще нет существующего родительского класса, то воспользуемся одиночным наследованием, если есть – множественным (когда множественное наследование невозможно, TreeAccessorDelegate – делается интерфейсом). Подобное смешивание классов проще, чем добавление нового подкласса и реализация его операций по отдельности.
    В динамически типизированных языках вроде Smalltalk или Objective С такой подход требует интерфейса для регистрации уполномоченного в адаптере. Тогда TreeDisplay просто переадресует запросы уполномоченному. В системе NEXTSTEP этот подход активно используется для уменьшения числа подклассов. источник codelab.ru оригинал codelab.ru
    codelab.ru оригинал источник codelab.ru
  3. Параметризованные адаптеры.
    Обычно в Smalltalk для поддержки сменных адаптеров параметризуют адаптер одним или несколькими блоками.
    Конструкция блока поддерживает адаптацию без порождения подклассов. Блок может адаптировать запрос, а адаптер может хранить блок для каждого отдельного запроса. В нашем примере это означает, что TreeDisplay хранит 1 блок для преобразования узла в GraphicNode, а другой – для доступа к потомкам узла.
    Например, чтобы создать класс TreeDisplay для отображения иерархии каталогов, мы пишем:
     Параметризованный адаптер [smalltalk]  ссылка
    1. directoryDisplay :=
    2. (TreeDisplay on: treeRoot)
    3. getChiIdrenBlock:
    4. [:node | node getSubdirectories]
    5. createGraphicNodeBlock:
    6. [:node | node createGraphicNode]

    Если вы встраиваете интерфейс адаптации в класс, то этот способ дает удобную альтернативу подклассам. оригинал codelab.ru источник codelab.ru
    источник codelab.ru codelab.ru оригинал

Результаты

Результаты применения адаптеров объектов и классов различны.
Адаптер класса: оригинал codelab.ru источник codelab.ru
  1. Адаптирует Adaptee к Target, перепоручая действия конкретному классу Adaptee.
    Т.к. здесь напрямую используется наследование, данный паттерн не будет работать, если мы захотим одновременно адаптировать и класс, и его подклассы. источник codelab.ru оригинал codelab.ru
  2. Позволяет адаптеру Adapter заместить некоторые операции адаптируемого класса Adaptee, так как Adapter есть не что иное, как подкласс Adaptee оригинал codelab.ru источник codelab.ru
  3. Вводит только один новый объект.
    Чтобы добраться до адаптируемого класса, не нужно никакого дополнительного обращения по указателю (применительно к языкам имеющим указатели наподобие как в C++). оригинал codelab.ru источник codelab.ru
  источник codelab.ru оригинал codelab.ru
Адаптер объектов: codelab.ru источник codelab.ru оригинал
  1. Позволяет одному адаптеру Adapter работать со многим адаптируемыми объектами Adaptee
    Т.е. с самим Adaptee и его подклассами (если таковые имеются). Адаптер может добавить новую функциональность сразу всем адаптируемым объектам codelab.ru оригинал источник codelab.ru
  2. Затрудняет замещение операций класса Adaptee.
    Для этого потребуется породить от Adaptee подкласс и заставить Adapter ссылаться на этот подкласс, а не на сам Adaptee. оригинал codelab.ru codelab.ru источник

Пример

В многопользовательской системе определена сущность оператор(Operator), на которую возлагаются определенные обязанности: Operator. оригинал codelab.ru codelab.ru источник

Помимо этого в системе изначально используется класс пользователь(User), который хотя и содержит в своей внутренней структуре данные, необходимые для функционирования в роли оператора – но обладает совершенно другим интерфейсом: User. codelab.ru оригинал источник codelab.ru

Вариант – имплементировать интерфейс Operator в User, добавив недостающие методы – нам не очень подходит. Излишне загромождать класс User, выполняющий свои определенные функции – новыми методами, в каждом случае, когда нужно представить его в качестве другой сущности – не совсем правильно. Таким образом, получаем задачу – адаптировать интерфейс класса User к интерфейсу Operator. codelab.ru codelab.ru оригинал источник

В итоге - классический способ использования паттерна Адаптер: оборачиваем имеющийся класс User в новый класс адаптера(OperatorUser), и имплементируем целевой интерфейс Operator, используя интерфейс класса User: OperatorUserAdapter. codelab.ru источник codelab.ru оригинал

При этом в данном случае можно также реализовать и вариант двустороннего адаптера, заменив агрегирование сущности User – наследованием от нее. В итоге полученный адаптер мы сможем свободно (прозрачно) использовать и там где требуется Operator, и там, где User: OperatorUser. codelab.ru codelab.ru оригинал источник

Известные применения паттерна Адаптер (Adapter)

Пример, приведенный в разделе Условия, Задача, Назначение, заимствован из графического приложения ET++Draw, основанного на каркасе ЕТ++. ET++Draw повторно использует классы ЕТ++ для редактирования текста, применяя для адаптации класс TextShape.
В библиотеке Interviews 2.6 определен абстрактный класс Interactor для таких элементов пользовательского интерфейса, как полосы прокрутки, кнопки и меню. Есть также абстрактный класс Graphic для структурированных графических объектов: прямых, окружностей, многоугольников и сплайнов. оригинал codelab.ru источник codelab.ru
И Interactor, и Graphic имеют графическое представление, но у них разные интерфейсы и реализации (общих родительских классов нет), и значит, они несовместимы: нельзя непосредственно вложить структурированный графический объект, скажем, в диалоговое окно. codelab.ru оригинал источник codelab.ru
Вместо этого Interviews 2.6 определяет адаптер объектов GraphicBlock - подкласс Interactor, который содержит экземпляр Graphic. GraphicBlock адаптирует интерфейс класса Graphic к интерфейсу Interactor, позволяет отображать, прокручивать и изменять масштаб экземпляра Graphic внутри структуры класса Interactor. оригинал codelab.ru codelab.ru источник
Сменные адаптеры широко применяются в системе ObjectWorks\Smalltalk. В стандартном Smalltalk определен класс ValueModel для видов, которые отображают единственное значение. ValueModel определяет интерфейс value, value: для доступа к значению. Это абстрактные методы. Авторы приложений обращаются к значению по имени, более соответствующему предметной области, например width и width:, но они не обязаны порождать от ValueModel подклассы для адаптации таких зависящих от приложения имен к интерфейсу ValueModel. источник codelab.ru codelab.ru оригинал

Вместо этого ObjectWorks\Smalltalk включает подкласс ValueModel, называющийся PluggableAdaptor. Объект этого класса адаптирует другие объекты к интерфейсу ValueModel (value, value:). Его можно параметризовать блоками для получения и установки нужного значения. Внутри PluggableAdaptor эти блоки используются для реализации интерфейса value, value:. Этот класс позволяет также передавать имена селекторов (например, width, width:) непосредственно, обеспечивая тем самым некоторое синтаксическое удобство. Данные селекторы преобразуются в соответствующие блоки автоматически. оригинал codelab.ru источник codelab.ru

codelab.ru codelab.ru источник оригинал

Еще один пример из ObjectWorks\Smalltalk - это класс TableAdaptor. Он может адаптировать последовательность объектов к табличному представлению.В таблице отображается по одному объекту в строке. Клиент параметризует TableAdaptor множеством сообщений, которые используются таблицей для получения от объекта значения в колонках. codelab.ru codelab.ru источник оригинал
В некоторых классах библиотеки NeXT AppKit используются объекты-уполномоченные для реализации интерфейса адаптации. В качестве примера можно привести класс NXBrowser, который способен отображать иерархические списки данных. NXBrowser пользуется объектом-уполномоченным для доступа и адаптации данных. codelab.ru codelab.ru источник оригинал

Придуманная Скоттом Мейером (Scott Meyer) конструкция «брак по расчету» (Marriage of Convenience) это разновидность адаптера класса. Мейер описывает, как класс FixedStack адаптирует реализацию класса Array к интерфейсу класса Stack. Результатом является стек, содержащий фиксированное число элементов. оригинал codelab.ru codelab.ru источник

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

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

изменяет его интерфейс. codelab.ru источник оригинал codelab.ru

  оригинал codelab.ru источник codelab.ru



Реализации: java(4)   +добавить реализацию

1) Operator.java, code #437[автор:this]
2) User.java, code #438[автор:this]
3) OperatorUserAdapter.java, code #439[автор:this]
4) OperatorUser.java, code #440[автор:this]


<< назад наверх
распечатать обсудить >>

 
каталог | задачи | паттерны | исходники | стат | форумы | карта сайта | контакты | ссылки 
© 2000-2017 CodeLAB Group
  Все права защищены
Страница сгенерирована за 0.045102 секунд
Количество запросов к БД: 14, gzip: 19.9kb/80.5kb(76%)