Съемный адаптер, как указано в GOF - PullRequest
3 голосов
/ 20 апреля 2020

Связанные посты в потоке Stackover для этой темы c: Пост_1 и Пост_2

Выше посты хороши, но все же я не смог получить ответ на свой путаница, поэтому я выкладываю это как новый пост здесь.
МОИ Вопросы на основе элементов многоразового объектно-ориентированного программного обеспечения GOF содержание книги о сменных адаптерах (упоминается после вопросов ниже), поэтому я был бы признателен, если бы обсуждения / ответы / комментарии были более сосредоточены на существующих примерах от GOF относительно подключаемых адаптеров, а не на других примерах

Q1) Что мы подразумеваем под встроенным адаптация интерфейса ?
Q2) Чем отличается сменный интерфейс от обычных адаптеров? Обычные адаптеры также адаптируют один интерфейс к другому.
Q3) Даже в обоих случаях использования мы видим оба метода извлеченного «узкого интерфейса» GetChildren(Node) и CreateGraphicNode(Node) в зависимости от Node. Node является внутренним для Инструментария. Является ли Node тем же, что и GraphicNode, и передается ли параметр CreateGraphicNode только для заполнения состояний, таких как (name, parentID и т. Д. c), уже созданного объекта Node?

Согласно GOF (я выделил несколько слов / предложений жирным шрифтом, чтобы подчеркнуть содержание, относящееся к моим Вопросам)

ObjectWorks \ Smalltalk [Par90] использует термин подключаемый адаптер для описания классов с адаптацией встроенного интерфейса .

Рассмотрим виджет TreeDisplay , который может графически отображать древовидные структуры. Если бы это был виджет специального назначения для использования только в одном приложении, то мы могли бы потребовать, чтобы отображаемые на нем объекты имели определенный интерфейс c; то есть все должны происходить из абстрактного класса Tree. Но если бы мы хотели сделать TreeDisplay более пригодным для повторного использования (скажем, мы хотели бы сделать его частью набора полезных виджетов), то это требование было бы необоснованным. Приложения будут определять свои собственные классы для древовидных структур . Их не следует заставлять использовать наш абстрактный класс Tree. Разные древовидные структуры будут иметь разные интерфейсы.

Съемные адаптеры . Давайте рассмотрим три способа реализации подключаемых адаптеров для описанного ранее виджета TreeDisplay, который может автоматически размечать и отображать иерархическую структуру. Первый шаг, который является общим для всех трех реализаций, обсуждаемых здесь, состоит в том, чтобы найти «узкий» интерфейс для Adaptee, то есть наименьшее подмножество операций, которое позволяет нам выполнять адаптацию. Узкий интерфейс, состоящий всего из нескольких операций, легче адаптировать, чем интерфейс с десятками операций. Для TreeDisplay адаптируемой является любая иерархическая структура. Минималистский интерфейс может включать в себя две операции: одну, которая определяет, как графически представить узел в иерархической структуре, и другую, которая извлекает дочерние узлы.

Тогда есть два варианта использования

  1. «Узкий интерфейс» выполнен как абстрактный и является частью класса TreeDisplay enter image description here

  2. Узкий интерфейс выделен как отдельный интерфейс и имеющий его состав в классе TreeDisplay enter image description here

(есть и третий подход к параметризованному адаптеру, но пропущенный для простоты, также этот 3-й один, я думаю, более конкретный c для разговоров)

Ответы [ 3 ]

3 голосов
/ 20 апреля 2020

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

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

Q1) Это то, что GoF подразумевает под адаптацией встроенного интерфейса : интерфейс встроенный в API Target для поддержки будущих адаптаций.

Q2) Как уже упоминалось, это довольно необычный сценарий для адаптера, поскольку типичная сила шаблона заключается в его способность обрабатывать API, которые не имеют общего дизайна.

GoF перечисляет три различных подхода к разработке Target API для адаптации. Первые два узнаваемы как пара их шаблонов поведения.

  1. Метод шаблона
  2. Стратегия
  3. Замыкания (то, что Smalltalk называет кодовые блоки )

Q3) Не вдаваясь в подробности примеров GUI GoF, основная идея c, лежащая в основе разработки так называемого «узкого интерфейса», заключается в удалить как можно больше доменной специфичности. В Java отправной точкой для доменного API c почти наверняка будут функциональные интерфейсы .

API Target с зависимостями от этих интерфейсов должно быть очень проще адаптировать, чем API, построенный вокруг доменных методов c. Первый позволяет создавать сменные адаптеры, тогда как второму потребуется более типичный адаптер с интенсивным отображением между API.

3 голосов
/ 21 апреля 2020

Позвольте мне поделиться несколькими мыслями.

Во-первых, поскольку вопрос был опубликован с тегом Smalltalk, я буду использовать синтаксис Smalltalk, который является менее подробным (например, #children вместо * 1004). *, et c.)

В качестве введения к этой проблеме (которая может быть полезна для некоторых читателей), скажем, что (generi c) фреймворки должны использовать язык c generi ( например, #children). Однако общие термины c могут быть не естественными для указанного объекта c, который вы рассматриваете. Например, в случае файловой системы обычно используется #files, #directories и т. Д. c., Но может отсутствовать селектор #children. Даже если добавление этих селекторов никого не убьет, вы не хотите заполнять свои классы новыми селекторами «generi c» каждый раз, когда «абстрактный» класс налагает свои соглашения об именах. В реальной жизни, если вы сделаете это, рано или поздно у вас возникнут коллизии с другими структурами, для которых тот же самый селектор имеет другое значение. Это подразумевает, что каждая структура имеет потенциал создания некоторого импеданса (т. Е. Трения) с объектами, которые пытаются извлечь из этого выгоду. Ну, адаптеры предназначены для смягчения этих побочных эффектов.

Есть несколько способов сделать это. Один делает ваш каркас подключаемый . Это означает, что вы не будете требовать от клиентов реализации определенного поведения c. Вместо этого вы попросите клиентов предоставить селектор или блок, вычисление которого приведет к требуемому поведению.

В примере Справочника, если ваш класс Справочник реализуется, скажем #entities, тогда вместо создания #children как синоним, вы скажете соответствующему классу в рамках что-то вроде childrenSelector: #entities. Объект, получающий этот метод, затем «подключит» (помните), что он должен отправить вам #entities при поиске children. Если у вас нет такого метода, вы все равно можете обеспечить требуемое поведение, используя блок, который делает то, что нужно. В нашем примере блок будет выглядеть следующим образом:

   childrenSelector: [self directories, self files].

( Примечание: сменная структура может предоставить синоним #childrenBlock:, чтобы сделать его интерфейс более дружественным. В качестве альтернативы, он может обеспечить более общий селектор, такой как childrenGetter:, et c.)

Теперь получатель будет хранить блок в childrenGetter ivar и будет оценивать его каждый раз, когда ему нужны дочерние элементы клиента.

Другое решение, которое можно рассмотреть, состоит в том, чтобы потребовать от клиента подкласса абстрактного класса. Это имеет то преимущество, что очень четко показывает поведение клиента. Однако обратите внимание, что у этого решения есть некоторые недостатки, поскольку в Smalltalk вы можете наследовать только от одного родителя. Таким образом, введение суперкласса может привести к нежелательному (или даже невыполнимому) ограничению.

Другой вариант, который вы упоминаете, состоит в добавлении одного косвенного обращения к предыдущему: вместо подкласса основного «объекта» вы предлагаете реферат суперкласс для создания подкласса поведения, который ваш объект должен адаптировать. Это похоже на первый подход в том, что вам не нужно менять клиента, за исключением того, что на этот раз вы помещаете адаптированный протокол в класс самостоятельно. Таким образом, вместо вставки нескольких параметров в каркас, вы помещаете их все в объект и передаете (или «подключаете») этот объект в каркас. Обратите внимание, что эти приспосабливающиеся объекты действуют как обертки в том смысле, что они знают реальную вещь и знают, как с ней обращаться для перевода тех немногих сообщений, которые должна отправить инфраструктура. В общем, использование оболочек обеспечивает большую гибкость за счет заполнения вашей системы большим количеством классов (что влечет за собой риск дублирования иерархий). Кроме того, упаковка многих объектов может повлиять на производительность вашей системы. Заметьте, кстати, что GraphicNode также выглядит как оболочка внутреннего / фактического Node.

Я не уверен, что ответил на ваш вопрос, но так как вы попросили меня как-то расширить мой комментарий Я с радостью попробовал.

3 голосов
/ 20 апреля 2020

Q1) Адаптация интерфейса означает просто адаптацию одного интерфейса для реализации другого, т. Е. Для чего нужны адаптеры. Я не уверен, что они имеют в виду под «встроенным», но это звучит как специфическая c особенность Smalltalk, с которой я не знаком.

Q2) «Сменный адаптер» класс адаптера, который реализует целевой интерфейс, принимая реализации для его отдельных методов в качестве аргументов конструктора. Цель состоит в том, чтобы позволить адаптерам быть выраженными кратко . Во всех случаях это требует, чтобы целевой интерфейс был небольшим, и обычно требуется какое-то языковое средство для краткого предоставления вычислений - лямбда, делегат или подобное. В Java средство для встроенных классов и функциональных интерфейсов означает, что указывать c класс адаптера, который принимает лямбда-аргументы, не нужно.

Сменные адаптеры - это удобство . Они не важны за этим. Однако ...

Q3) Приведенный текст не касается подключаемых адаптеров, и ни в одном из двух вариантов использования нет подключаемого адаптера. Эта часть о принципе разделения интерфейсов, и это важно .

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

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

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

...