Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:42 1 Метафора
03:27 2 Программный код метафоры
07:56 3 Структура паттерна на языке UML (Адаптер уровня классов)
10:27 4 Структура паттерна на языке UML (Адаптер уровня объектов)
11:34 5 Структура паттерна на языке C# (Адаптер уровня классов)
12:33 6 Структура паттерна на языке C# (Адаптер уровня объектов)
15:15 7 Назначение паттерна
17:43 10 Участники и отношение между ними
17:43 8 Участники и отношение между ними
17:55 9 Мотивация
18:18 11 Применимость и результаты
18:33 12 Разновидности адаптеров
20:07 13 «Когда использовать паттерн?»

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

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку Скачать материалы урока

Паттерн Adapter

Название

Адаптер

Также известен как

Wrapper (обертка)

Классификация

По цели: структурный

По применимости: к классам и объектам

Частота использования

Выше средней    -   1 2 3 4 5

Назначение

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

Введение

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

Например, ноутбук, произведенный согласно стандартам и требованиям к сети электрического питания США, имеет определенную входную силу тока и напряжение, а также подключается к сети электрического питания с использованием вилки типа “B”. Вилка подключения к электросети этого стандарта имеет 2 плоских контакта и один круглый контакт для заземления, такой тип также известен как PBG (Parallel Blade with Ground) или Hubbell (по имени известного производителя электрофурнитуры).

Владелец такого ноутбука смог бы без проблем подключиться к сети питания в Канаде, Японии и Мексике, так как в этих странах используется такой же стандарт подключения, как и в США.  Но как быть, если владельцу такого ноутбука необходимо поехать Великобританию, где принят стандарт розеток типа «G» (три больших плоских контакта, расположенных треугольником) или какую-нибудь другую Европейскую страну, где принят стандарт «F» (Schuko) (два круглых контакта и две заземляющих клеммы вверху и внизу розетки)? Сложности в этой ситуации добавляет еще и тот факт, что стандарт Великобритании на электрические сети и подключение к ним допускает использование в сети тока большой силы, и поэтому обязательным является использование предохранителя непосредственно в вилке подключения к сети.

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

Разумеется, есть другой, более рациональный подход – использование адаптеров. То есть приспособлений, которые позволяют «сдружить» различные стандарты подключения электробытовых устройств, таким образом, чтобы выполнялись требования и адаптируемого устройства - вилки ноутбука и электрической розетки, к которой необходимо адаптироваться. При этом, в зависимости от страны в которой пребывает владелец ноутбука, он может использовать различные адаптеры, позволяющие адаптировать ноутбук к стандартам этой страны.

Структура паттерна на языке UML

Adapter уровня классов

См. пример к главе: \006_Adapter\001_Adapter (Class)

Adapter уровня объектов

См. пример к главе: \006_Adapter\001_Adapter (Object)

Структура паттерна на языке C#

Adapter уровня классов

См. пример к главе: \006_Adapter\001_Adapter (Class)

Adapter уровня объектов

См. пример к главе: \006_Adapter\001_Adapter (Object)

Участники

  • Target - Цель:

Формирует требуемый клиенту интерфейс (набор имен методов).

  • Client - Клиент:

Пользуется объектами с интерфейсом Target.

  • Adaptee - Адаптируемый:

Содержит интерфейс (набор методов) требующий адаптации.

  • Adapter - Адаптер

  Адаптирует интерфейс Adaptee к интерфейсу Target.

Отношения между участниками

Отношения между классами (для адаптера уровня классов)

  • Класс Adapter связан связью отношения наследования с классом Adaptee и связью отношения реализации с интерфейсом ITarget.

Отношения между классами (для адаптера уровня объектов)

  • Класс Adapter связан связью отношения наследования с абстрактным классом Target и связью отношения ассоциации с классом Adaptee.

Отношения между объектами

  • Клиент вызывает методы на экземпляре класса Adapter. Adapter в свою очередь вызывает методы адаптируемого объекта или класса Adaptee.

Мотивация

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

Реализовать класс Line легко, так как выполнения каких-либо сложных действий с линиями в программе не предусматривается. Совсем по-другому обстоит ситуация с классом TextShape. Реализовать в нем операции отображения и перемещения по рабочей области значительно сложнее. Можно было бы воспользоваться имеющимся классом TextView (условимся, что TextView приобретен у стороннего производителя элементов управления), в котором уже реализован необходимый функционал, позволяющий отображать текст нужным образом. В силу обстоятельств, класс TextView, не является производным от класса Shape, поскольку разрабатывался сторонними производителями, и его нельзя напрямую использовать в базовом классе редактора DrawingEditor.

Для организации использования класса TextView в данном проекте требуется изменить его интерфейс так, чтобы он соответствовал интерфейсу типа Shape. Так как класс TextView разрабатывался сторонними разработчиками, то доступ к его исходному коду может отсутствовать. Но, даже при наличии исходного кода, неразумно вносить в него изменения, поскольку нелогично подключаемые библиотеки приспосабливать к интерфейсу целевой программы. Правильным решением проблемы несовместимости интерфейсов, является адаптация интерфейса класса TextView к интерфейсу класса Shape. Такую адаптацию можно организовать следующим образом: при создании класса TextShape следует включить в него функциональность из класса TextView через использование композиции, при этом реализовать в классе TextShape интерфейс типа Shape. Реализация интерфейса типа Shape (в данном случае метода BoundingBox) в классе TextShape будет служить своеобразной оберткой над методом GetExtend из класса TextView. В таком случае класс TextShape будет выступать в роли адаптера (уровня объектов), и графический редактор сможет воспользоваться функциональностью класса TextView.

Зачастую, на практике адаптируемый класс не всегда предоставляет всю необходимую разработчикам функциональность, и в таком случае на класс-адаптер возлагается ответственность за добавление новой и расширение существующей функциональности. Класс TextView не предоставляет возможности перемещать текст по рабочей области, но класс TextShape, такую возможность может предоставить, через реализацию абстрактного метода CreateManipulator из базового класса Shape. Метод CreateManipulator возвращает экземпляр класса TextManipulator приведенный к базовому типу Manipulator. Абстрактный класс Manipulator – класс объектов, которым должно быть известно, как анимировать экземпляры объектов типа Shape, при их перетаскивании мышью по рабочей области окна. Для работы с каждым типом объектов производных от Shape (Line, TextShape) у класса Manipulator имеются подклассы LineManipulator и TextManipulator. Таким образом, объект класса TextShape расширяет возможности класса TextView, для его использования в приложении графического редактора.

См. пример к главе: \006_Adapter\002_DrawingEditor

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

Паттерн Adapter рекомендуется использовать, когда:

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

Результаты

Варианты использования паттерна Adapter уровня классов и паттерна Adapter уровня объектов, имеют свои особенности.

Adapter уровня классов

  • Адаптирует интерфейс Adaptee к интерфейсу ITarget, делегируя ответственность за выполнение операций (методов) конкретному классу Adaptee.
  • Позволяет классу Adapter переопределить или заместить некоторые операции из базового класса Adaptee.
  • Оставляет возможность создания только одного экземпляра класса Adapter, так как Adapter наследуется от Adaptee, то не придётся отдельно создавать экземпляр Adaptee внутри класса Adapter.

Adapter уровня объектов

  • Позволяет одному экземпляру класса Adapter, работать с разными адаптируемыми объектами типа Adaptee, как с экземплярами класса Adaptee, так и с экземплярами классов производных от Adaptee.
  • Позволяет классу Adapter расширить некоторые операции экземпляра класса Adaptee (например, в методе класса Adapter сперва выполняется некоторая своя функциональность, а после вызывается метод экземпляра класса Adaptee).
  • Затрудняет расширение (переопределение или замещение) защищенных виртуальных (protected virtual) и не виртуальных методов из класса Adaptee. Для расширения защищенных методов потребуется создать класс DerivedAdaptee производный от класса Adaptee, внести в него необходимые изменения и в адаптере использовать экземпляр класса DerivedAdaptee.

Особенности применения паттерна Adapter

  • Объем работ по адаптации.

Устройство разных адаптеров может сильно отличаться. Класс Adapter может просто изменять имена методов класса Adaptee, но может и расширять методы класса Adaptee, при этом добавляя свои новые методы с использованием которых формируется желаемый (требуемый) интерфейс взаимодействия с объектом класса Adapter.

  • Сменные адаптеры.

Степень многократного использования (reusable) класса «А» будет высокой, тогда и только тогда, когда разработчик класса «А» не будет делать предположений о том, какие классы будут использовать класс «А».

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

Имеется разновидность адаптеров, которые называются сменными адаптерами (pluggable adapters). Сменный адаптер, это такой адаптер PluggableAdapter, в который встроен механизм адаптации интерфейсов нескольких адаптируемых классов AdapteeA, AdapteeB и т.д.

См. пример к главе: \006_Adapter\003_PluggableAdapter

Рассмотрим элемент управления TreeView, который отображает древовидные структуры. Разные классы имеют свои собственные интерфейсы для получения доступа к элементам древовидных структур, которые эти классы представляют.

Например, можно сравнить интерфейсы взаимодействия с классами DirectoryInfo и Assembly. Как можно убедиться, интерфейсы этих классов различны. Класс TreeView не адаптирован для работы с классами DirectoryInfo и Assembly напрямую. Клиент должен самостоятельно назначать данные узлам TreeNode, класса TreeView, для отображения этих данных в дереве. Для упрощения работы клиента, есть смысл создать пользовательский элемент управления – класс TreeDisplay, для отображения древовидных структур. Элемент управления типа TreeDisplay должен уметь отображать иерархии обоих видов древообразных структур, как DirectoryInfo, так и Assembly. Другими словами, в TreeDisplay должна быть встроена адаптация интерфейсов классов DirectoryInfo и Assembly.

См. пример к главе: \006_Adapter\004_AdapterTreeDisplay

  • Двусторонние адаптеры.

Важное правило: клиент не должен знать, что используемый им объект является адаптером. Объект класса Adapter не обладает открытым интерфейсом класса Adaptee, поэтому объект класса Adapter невозможно использовать там, где может использоваться объект класса Adaptee.

Имеется разновидность адаптеров, которые называются двусторонними адаптерами (two-way adapters). Двусторонние адаптеры способны обеспечить возможность использовать адаптер TwoWayAdapter там, где мог использоваться Adaptee.

Интерфейс двустороннего адаптера (класса TwoWayAdapter) представляет собой объединение интерфейсов адаптируемых друг к другу классов AdapteeNew и AdapteeOld.

См. пример к главе: \006_Adapter\005_TwoWayAdapter

Реализация

Имеется три подхода к реализации сменных адаптеров. Эти способы проще рассмотреть на примере использования пользовательского элемента управления TreeDisplay, для автоматического отображения иерархических древовидных структур (TreeDisplay уже был описан выше).

  • Первый подход – использование абстрактных операций.

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

См. пример к главе: \006_Adapter\006_3_AdaptersForTreeDisplay

  • Второй подход – использование объектов-уполномоченных.

Для того чтобы реализовать данный подход потребуется создать специальные объекты – «объекты-уполномоченные». Тогда класс TreeDisplay сможет переадресовывать запросы для доступа к иерархической структуре этим объектам-уполномоченным, при этом, реализовывая различные стратегии адаптации путем использования необходимых конкретных уполномоченных. Например, одним из таких объектов-уполномоченных для класса TreeDisplay мог бы быть класс DirectoryBrowser. В статически типизированных языках, которым и является C#, потребуется явно задать интерфейс, который необходим классу TreeDisplay, например, interface ITreeAccessorDelegate, и реализовать этот интерфейс в выбранном классе-уполномоченном DirectoryBrowser.

  • Третий подход – адаптеры, конфигурируемые при помощи параметров.

Этот подход предоставляет возможность использования конструкции switch или нескольких условных конструкций if-else в теле адаптера, как альтернативу наследованию при реализации сменных адаптеров. Такой подход позволяет реализовывать механизмы адаптации без порождения подклассов. Применимо, к рассматриваемому примеру это означает, что в классе TreeDisplay может быть два условных блока: первый блок, для преобразования узла в тип GraficNode, а второй блок – для доступа к потомкам узла.

Пример кода

Предлагается рассмотреть реализацию адаптера уровня класса и адаптера уровня объекта на примере, из раздела «Мотивация»:

В классе Shape создается абстрактный фабричный метод CreateManipulator, с возвращаемым значением типа Manipulator. Manipulator обладает функциональностью по анимированию объектов класса Shape в ответ на действия пользователя. В классе Shape также создается абстрактный метод BoundingBox, который отвечает за отображение элементов типа Shape на форме (TextShape и Line).

public abstract class Shape
    {
        public abstract void BoundingBox();
        public abstract Manipulator CreateManipulator();
    }

Абстрактный класс Manipulator реализует взаимодействие и контролирует состояние объекта-манипулятора. Манипуляторы представлены как наследники базового класса Control.

public abstract class Manipulator : Control
    {
        //состояние манипулятора на форме.
        public Point StartPoint { get; protected set; }
        public Point EndPoint { get; protected set; }
       
        //задание базовых параметров контрола
        public Manipulator()
        {
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            SetStyle(ControlStyles.Opaque, true);
            this.BackColor = Color.Transparent;
            this.DoubleBuffered = false;
        }

        //определяет параметры необходимые при создании контрола
        protected override CreateParams CreateParams
        {
            get
            {
                const int WS_EX_TRANSPARENT = 0x00000020;
                CreateParams createParams = base.CreateParams;
                createParams.ExStyle |= WS_EX_TRANSPARENT;
                return createParams;
            }
        }
    }

Класс TextView содержит метод GetExtend, который отвечает за отображение объектов на форме, этот метод является аналогом метода BoundingBox, а аналог фабричного метода для создания манипулятора в классе TextView отсутствует. Таким образом для работы с классом TextView в проекте необходимо использовать класс-адаптер TextShape, который адаптирует интерфейс класса TextView к интерфейсу класса Shape.

class TextView
    {
        Label label;

        public TextView()
        {
            label = new Label();
        }

        public Label GetExtend()
        {
            label.ForeColor = Color.DarkMagenta;
            label.Font = new Font(label.Font, label.Font.Style | FontStyle.Bold);
            label.BorderStyle = BorderStyle.None;
            label.TextAlign = ContentAlignment.TopLeft;
            label.FlatStyle = FlatStyle.Standard;
            label.AutoSize = true;

            return label;
        }
    }

    class TextShape : Shape
    {
        Point startPoint;
        string text;
        TextView textView;
        Label label;

        public TextShape(Point startPoint, string text)
        {
            textView = new TextView();
            this.startPoint = startPoint;
            this.text = text;
        }

        // Преобразование запроса BoundingBox в запрос GetExtend.
        public override void BoundingBox()
        {
            label = textView.GetExtend();
            label.Text = text;
        }

// Фабричный метод, возвращающий манипулятор соответствующий 
// конкретной фигуре
        public override Manipulator CreateManipulator()
        {
            return new TextManipulator(startPoint, label);
        }
    }

Поскольку в классе TextView не предусмотрен метод для создания манипулятора, который будет работать с текстом, то необходимо такой метод реализовать самостоятельно. В классе TextShape реализация метода CreateManipulator не использует повторно никакой функциональности из класса TextView.

class TextManipulator : Manipulator
    {
        Label textInside;
        private int deltaX, deltaY;
        Point currentMouse;
        bool mousePressed = false;

// Метод, возвращающий глобальные координаты курсора на 
// контроле относительно формы 
        Point GetMouseLocation(Point curMouse)
        {
return new Point(curMouse.X + this.Location.X, curMouse.Y +    this.Location.Y);
        }

        // Перерисовка манипулятора
        void ReDrawControl(Point startPoint)
        {
            Size = new Size(textInside.Text.Length * 8, textInside.Size.Height);
            Location = startPoint;
            RecreateHandle();
            Refresh();
        }

        public TextManipulator(Point startPoint, Label label)
        {
            textInside = label;
            StartPoint = startPoint;
            ReDrawControl(startPoint);
            Controls.Add(textInside);
            textInside.MouseDown += TextInside_MouseDown;
            textInside.MouseUp += TextInside_MouseUp;
        }

        // Методы обработчики базового класса Manipulator
        void TextInside_MouseDown(object sender, MouseEventArgs e)
        {
            OnMouseDown(e);
        }

        void TextInside_MouseUp(object sender, MouseEventArgs e)
        {
            OnMouseUp(e);
        }








        // Метод, определяющий новые координаты манипулятора на форме
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (mousePressed)
            {
                base.OnMouseUp(e);
                deltaX = GetMouseLocation(e.Location).X - currentMouse.X;
                deltaY = GetMouseLocation(e.Location).Y - currentMouse.Y;
                StartPoint = new Point(StartPoint.X + deltaX, StartPoint.Y + deltaY);
                ReDrawControl(StartPoint);
                currentMouse = GetMouseLocation(e.Location);
            }
            else
                mousePressed = false;
            Cursor = Cursors.Arrow;
        }

        // Определение координат текущего манипулятора по клику
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            currentMouse = GetMouseLocation(e.Location);
            mousePressed = true;
            Cursor = Cursors.Hand;
        }
    }

Известные применения паттерна в .Net

Паттерн Adapter, имеет одно из выражений в платформе .Net в виде идеи использования перегрузки операторов преобразования значения типа (explicit / implicit). А также:

System.Web.Configuration.AdapterDictionary

http://msdn.microsoft.com/ru-ru/library/system.web.configuration.adapterdictionary.aspx

System.AddIn.Pipeline.AddInAdapterAttribute

http://msdn.microsoft.com/ru-ru/library/system.addin.pipeline.addinadapterattribute.aspx

System.AddIn.Pipeline.CollectionAdapters

http://msdn.microsoft.com/ru-ru/library/system.addin.pipeline.collectionadapters(v=vs.90).aspx

System.AddIn.Pipeline.ContractAdapter

http://msdn.microsoft.com/ru-ru/library/system.addin.pipeline.contractadapter(v=vs.90).aspx

System.AddIn.Pipeline.HostAdapterAttribute

http://msdn.microsoft.com/ru-ru/library/system.addin.pipeline.hostadapterattribute(v=vs.90).aspx

System.Data.Common.DataAdapter

http://msdn.microsoft.com/en-us/library/system.data.common.dataadapter(v=vs.110).aspx

System.Data.Common.DbDataAdapter

http://msdn.microsoft.com/en-us/library/system.data.common.dbdataadapter(v=vs.110).aspx

System.Data.Odbc.OdbcDataAdapter

http://msdn.microsoft.com/ru-ru/library/system.data.odbc.odbcdataadapter(v=vs.110).aspx

System.Data.OleDb.OleDbDataAdapter

http://msdn.microsoft.com/en-us/library/system.data.oledb.oledbdataadapter(v=vs.110).aspx

System.Data.OracleClient.OracleDataAdapter

http://msdn.microsoft.com/en-us/library/system.data.oracleclient.oracledataadapter(v=vs.110).aspx

System.Data.SqlClient.SqlDataAdapter

http://msdn.microsoft.com/ru-ru/library/system.data.sqlclient.sqldataadapter(v=vs.110).aspx

System.Web.UI.Adapters.ControlAdapter

http://msdn.microsoft.com/en-us/library/system.web.ui.adapters.controladapter(v=vs.110).aspx

System.Web.UI.Adapters.PageAdapter

http://msdn.microsoft.com/en-us/library/system.web.ui.adapters.pageadapter(v=vs.110).aspx

System.Web.UI.MobileControls.DesignerAdapterAttribute

http://msdn.microsoft.com/ru-ru/library/system.web.ui.mobilecontrols.designeradapterattribute(v=vs.110).aspx

System.Web.UI.WebControls.Adapters.DataBoundControlAdapter

http://msdn.microsoft.com/ru-ru/library/system.web.ui.webcontrols.adapters.databoundcontroladapter(v=vs.90).aspx

System.Web.UI.WebControls.Adapters.WebControlAdapter

http://msdn.microsoft.com/ru-ru/library/system.web.ui.webcontrols.adapters.webcontroladapter(v=vs.90).aspx

System.Web.UI.WebControls.Adapters.MenuAdapter

http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.adapters.menuadapter(v=vs.110).aspx

И т.д.

© 2017 ITVDN, все права защищены