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

Основные темы, рассматриваемые на уроке:
00:56 1 Метафора
06:00 2 Пример
10:50 3 Работа в Visual studio (Пример)
27:13 4 Структура паттерна на языке UML
31:08 5 Структура паттерна на языке C#
33:03 6 Назначение паттерна
33:36 7 «Когда использовать паттерн ?»

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

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

Паттерн Bridge  

Название 

Мост 

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

Handle/Body (описатель/тело) 

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

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

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

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

Средняя               -   1 2 3 4 5 

Назначение 

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

Введение 

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

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

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

Рассмотрим такую абстракцию из объективной реальности как компьютер. Что такое компьютер? Компьютер  это собирательное понятие описывающее устройство, состоящее из конкретных частей  монитор, системный блок, клавиатура, манипулятор-мышь. Но кто-то возразит и предложит рассматривать системный блок как абстракцию, состоящую из конкретных частей таких как  корпус, блок питания, жесткий диск, материнская плата, процессор, ОЗУ, видеокарта, дисковод. Но, и тут можно возразить, и предложить рассмотреть более детально устройство процессора, и представить процессор как абстракцию, состоящую из триггеров и других элементов. Триггеры можно в свою очередь представить в форме абстракции и разложить на более мелкие элементы, например, транзисторы. Транзистор тоже можно представить, как абстракцию, состоящую из трех электродов (эмиттер, база, коллектор) и небольшого количества кремния. А что такое атом кремния?  это абстракция. Атом - собирательное понятие из нейтронов, протонов и электронов. А что такое нейтрон? … Так можно войти в паралич анализа и никогда не закончить декомпозицию каждой составляющей компьютера раскладывая на атомы все его составляющие. Когда же следует закончить декомпозицию и очередную абстракцию представить конкретикой? Тогда, когда мы согласны работать с устройством как с кибернетически черным ящиком (не понимая, как устройство функционирует) и готовы пренебречь гибкостью сопровождения данного устройства. Например, если вышло из строя устройство для чтения DVD дисков, то один человек просто заменит устройство на новое, а другой человек попытается его починить (обладая соответствующими знаниями).  

Рассмотрим абстракцию из виртуальной реальности. Например, объект класса OpenFileDialog. Что такое OpenFileDialog это собирательное понятие, состоящее из набора элементов, представляющее собой диалоговое окно для выбора файла. Но, OpenFileDialog  конкретный класс, он спроектирован так, чтобы представлять собой точный единичный объект без возможности его расширения (sealed). Разработчики этого класса не рекомендуют воспринимать его как абстрактное понятие, и соответственно не рекомендуют детально разбираться в его устройстве. Рекомендуется им просто пользоваться как черным ящиком, а настоящая абстракция расположена выше в графе наследования  в классе FileDialog. 

 

Граф наследования класса OpenFileDialog устроен так:  

              sealed class OpenFileDialog : FileDialog {…} 

в свою очередь:  abstract class FileDialog : CommonDialog {…} 

в свою очередь:  abstract class CommonDialog : Component {…} 

в свою очередь: class Component : MarshalByRefObjectIComponentIDisposable {…} 

 

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

Если некоторая абстракция подразумевает несколько реализаций (конкретик), то обычно применяют наследование. Например, абстракция FileDialog имеет две конкретные реализации, это классы OpenFileDialog и SaveFileDialog. Абстракция задает общий интерфейс взаимодействия с конкретными реализациями, тем самым формируя понятие общего типа. Конкретные классы могут по-разному реализовать абстрактный интерфейс, главное, чтобы имена методов, составляющих общий интерфейс взаимодействия, соответствовали роду выполняемой ими деятельности. 

Но, подход с наследованием разрушает гибкость и формирует хрупкость. Хрупкость базового класса  фундаментальная проблема в ООП. Проблема хрупкого базового класса заключается в том, что изменения, внесенные в реализацию базового класса, могут негативно сказаться на работе производных классов. Наследование формирует сильнейшую форму связанности (coupling - мера зависимости) из всех возможных и разорвать эту связанность невозможно. Соответственно потеря гибкости означает  затруднение модификации, расширения и повторного использования как абстракции, так и реализации (конкретики). Использование паттерна Bridge позволяет решить описанные выше проблемы.  

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

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

Сам треугольник  это абстракция, а те части из которых он состоит  есть элементы реализации абстракции. Для школьного треугольника и треугольника для бильярда реализацией будут те деревянные стороны из которых он состоит, а для музыкального треугольника реализацией будет являться изогнутый металлический прут. 

Для нарисованного на листе бумаги треугольника реализацией будет являться краска карандаша, нанесенная на лист бумаги.  

Для нарисованного треугольника на Windows форме реализацией будет линия, вычерченная графическим устройством Graphics при помощи пера Pen. 

Ниже представлена диаграмма классов на которой представлены две иерархии: классы геометрических форм Shape (абстракций) и линий LineStyle (реализаций).

См. пример к главе: \007_Bridge\004_ Bridge WinForm (Shapes and Lines) 

 

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

См. пример к главе: \007_Bridge\001_Bridge 

 

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

См. пример к главе: \007_Bridge\001_Bridge 

  

Участники 

  • Abstraction - Абстракция: 

Предоставляет интерфейс для абстракции. Хранит ссылку на Implementor. 

  • RefinedAbstraction - Уточненная абстракция: 

Расширяет интерфейс, предоставляемый абстракцией.  

  • Implementor Реализатор: 

Предоставляет интерфейс для реализации. Чаще всего класс Implementor предоставляет низкоуровневый интерфейс, а Abstraction предоставляет высокоуровневый интерфейс. 

  • ConcreteImplementor Конкретный реализатор: 

Реализует интерфейс класса Implementor. 

 

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

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

  • Абстрактный класс Abstraction связан связью отношения агрегации с абстрактным классом Implementor. 

  • Конкретный класс RefinedAbstraction связан связью отношения наследования с абстрактным классом Abstraction.  

  • Конкретные классы-реализаторы ConcreteImplementorA и ConcreteImplementorB связаны связью отношения наследования с абстрактным классом Implementor. 

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

  • Объекты типа Abstraction перенаправляют запросы клиента объектам типа Implementor. 

 

Мотивация 

Предлагается рассмотреть такую разновидность абстракции, как «переносимая абстракция», когда абстракция и ее реализация помещаются в раздельные (параллельные или частично параллельные) иерархии классов. Рассмотрим использование переносимой абстракции на простом примере построения приложения, способного использовать разные стили пользовательского интерфейса (например, стиль Microsoft Windows и стиль Mac OS).  

Создадим две иерархии, одну для интерфейсов окон (WindowMSWindow и MacWindow), а другую для реализаций элементов управления определенного стиля (WindowImpMSWindowImp и MacWindowImp). Так, например, подкласс MSWindowImp предоставляет реализацию в стиле Microsoft Windows. 

Все методы классов (MSWindow и MacWindow) производных от класса Window, используют методы из классов (MSWindowImp и MacWindowImp) производных от класса WindowImp. Такой подход отделяет абстракцию окна определенного стиля (например, MSWindow) от деталей реализации каждого отдельного элемента стиля (FormButton и др.).  

На диаграмме классов видно, что класс Window является вершиной иерархии абстракций, а класс WindowImp является вершиной иерархии реализаций (параллельной иерархии абстракций).  Связь отношения агрегации между классами Window и WindowImp называется мостом. Мост между абстракцией (WindowMSWindow и MacWindow) и реализацией (WindowImpMSWindowImp и MacWindowImp). Это позволяет изменять абстракцию и реализацию независимо друг от друга. 

 

См. пример к главе: \007_Bridge\002_BridgeMotivation 

 

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

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

  • Требуется избежать постоянной привязки абстракции к реализации. Иногда бывает необходимо выбирать нужную реализацию во время выполнения программы.  

  • Требуется предоставить возможность расширения новыми подклассами и абстракции, и реализации.  

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

  • Необходимо избавиться от графов наследований, вложенных в графы наследования.

Параллельная иерархия всегда предпочтительней вложенного графа наследования.

Результаты 

Паттерн Bridge обладает следующими преимуществами: 

  • Отделение реализации от абстракции. 

Реализация (Implementor) не имеет привязки к абстрактному интерфейсу абстракции (Abstraction), как это могло бы быть в случае использования вложенных графов наследования. Реализацию абстракции (RefinedAbstraction) можно конфигурировать во время выполнения программы, просто подставляя объекты нужных классов (ConcreteImplementorA или ConcreteImplementorB). Разделение Abstraction и Implementor устраняет зависимости между абстракцией и реализацией во время компиляции, т.е., позволяет изменять абстракцию и реализацию независимо друг от друга. 

  • Повышение степени расширяемости. 

Имеется возможность независимо друг от друга расширять иерархии классов Abstraction и Implementor. 

  • Сокрытие реализации от клиента. 

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

 

Реализация 

Полезные приемы реализации паттерна Bridge: 

  • Наличие только одного класса Implementor. 

В том случае, если в программе имеется только одна реализация (ConcreteImplementor), то создавать абстрактный класс Implementor необязательно. Это частный случай использования паттерна Bridge, когда RefinedAbstraction ссылается на ConcreteImplementor. Тем не менее такое разделение полезно, так как позволит изменять ConcreteImplementor, при этом не перекомпилируя клиентскую часть кода. 

  • Создание нужного объекта класса ConcreteImplementor. 

Как принимается решение о том, экземпляр какого конкретного реализатора (ConcreteImplementorA или ConcreteImplementorB) требуется создать? Если у класса RefinedAbstraction имеется информация о классах ConcreteImplementor, то класс RefinedAbstraction в своем конструкторе может создать экземпляр нужного класса ConcreteImplementor. Например, если требуется создать коллекцию, поддерживающую несколько возможных реализаций, то решение о типе коллекции можно принять в зависимости от требуемого размера коллекции. Для хранения маленького числа элементов есть смысл создавать коллекцию типа ListDictionary, а для хранения большого числа элементов, коллекцию типа Hashtable. 

См. пример к главе: \007_Bridge\003_BridgeCollections  

 

Пример кода 

Предлагается рассмотреть пример, из раздела «Мотивация», где рассматривается структура приложения, использующего различные стили пользовательского интерфейса (например, стиль Microsoft Windows и стиль Mac OS). Рассмотрим класс Window, который стоит на вершине иерархии абстракций и задает высокоуровневую базовую абстракцию для окна клиентских приложений: 

abstract class Window 
    { 
        protected WindowImp implementor; 
        protected Form form; 
        protected Button button; 
 
        // Operation 
        public virtual void Draw() 
        { 
            this.form.Controls.Add(button); 
            Application.EnableVisualStyles(); 
            Application.Run(this.form); 
        }        
    }

Класс Window содержит поле implementor типа WindowImp. Тип WindowImp представлен в виде абстрактного класса, в котором задан интерфейс взаимодействия с оконной системой. WindowImp является вершиной иерархии реализаций: 

abstract class WindowImp 
    { 
        protected Button button; 
        protected Form form; 
        public abstract Form DevDrawForm(); 
        public abstract Button DevDrawButton(); 
    }

Классы MSWindow и MacWindow, производные от класса Window являются представителями иерархии абстракций и определяют разнообразные варианты окон с различным внешним видом форм и кнопок:

class MacWindow : Window 
    { 
        public MacWindow() 
        { 
            this.implementor = new MacWindowImp(); 
            this.form = this.implementor.DevDrawForm(); 
            this.button = this.implementor.DevDrawButton(); 
        } 
 
        // Operation 
        public override void Draw() 
        { 
            form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; 
            base.Draw(); 
        } 
    } 
 
 
    class MSWindow : Window 
    { 
        public MSWindow() 
        { 
            this.implementor = new MSWindowImp(); 
            this.form = this.implementor.DevDrawForm(); 
            this.button = this.implementor.DevDrawButton(); 
        } 
 
        // Operation 
        public override void Draw() 
        { 
            form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 
            base.Draw(); 
        } 
    } 

Конкретными представителями иерархии реализаций являются подклассы MacWindowImp и MSWindowImp класса WindowImp:

 class MacWindowImp : WindowImp 
    { 
        public override Form DevDrawForm() 
        { 
            this.form = new Form(); 
            this.form.AutoScaleDimensions = new SizeF(6F, 13F); 
            this.form.AutoScaleMode = AutoScaleMode.Font; 
            this.form.ClientSize = new Size(284, 172); 
 
            this.form.Name = "Mac Form"; 
            this.form.Text = "Mac OS - Snow Leopard"; 
            this.form.BackColor = Color.White; 
            return this.form; 
        } 
 
        public override Button DevDrawButton() 
        { 
            this.button = new Button(); 
            this.button.Location = new Point(75, 70); 
            this.button.Size = new Size(125, 25); 
            this.button.Text = "Leopard"; 
            this.button.ForeColor = Color.White; 
            this.button.BackColor = Color.LightGray; 
             
            return this.button; 
        } 
    } 
 
    class MSWindowImp : WindowImp 
    { 
        public override Form DevDrawForm() 
        { 
            this.form = new Form(); 
            this.form.AutoScaleDimensions = new SizeF(6F, 13F); 
            this.form.AutoScaleMode = AutoScaleMode.Font; 
            this.form.ClientSize = new Size(284, 172); 
 
            this.form.Name = "Microsoft Form"; 
            this.form.Text = "Windows Explorer"; 
            this.form.BackColor = Color.LightBlue; 
 
            return this.form; 
        } 
 
        public override Button DevDrawButton() 
        { 
            this.button = new Button(); 
            this.button.Location = new Point(75, 70); 
            this.button.Size = new Size(125, 25); 
            this.button.Text = "Windows"; 
            this.button.ForeColor = Color.Aqua; 
            this.button.BackColor = Color.DarkBlue; 
 
            return this.button; 
        } 
    }  

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