Содержание
Паттерн проектирования Мост
Паттерн Мост (Bridge) — это структурный паттерн проектирования, который позволяет нам отделить абстракции от реализаций и сделать их независимыми друг от друга. В результате абстракции и реализации могут разрабатываться как отдельные сущности. Паттерн Мост считается одним из лучших методов организации иерархии классов. Но давайте поподробнее разберем, что всё это значит.
Элементы паттерна Мост
- Abstraction (абстракция). Это ядро паттерна Мост. Она предоставляет ссылку на Implementer.
- Refined Abstraction (расширенная абстракция) содержит различные вариации управляющей логики, наследуется от Abstraction и расширяет унаследованный интерфейс.
- Implementer (реализатор). Определяет базовый интерфейс для конкретных реализаций. Этот интерфейс не обязательно должен напрямую соответствовать интерфейсу абстракции. Более того, он может сильно отличаться от него.
- Concrete Implementation (конкретная реализация) наследуется от Implementer.
[python_ad_block]
Какую проблему решает паттерн Мост?
Давайте представим, что у нас есть класс Cuboid
(параллелепипед), который имеет три атрибута: length
(длина), breadth
(ширина) и height
(высота). Также он имеет три метода: ProducewithAPI()
, ProduceWithAPI2()
и expand()
.
Два метода зависят от реализации, поскольку у нас есть два API, а один метод — expand()
— не зависит.
Пока у нас всего три метода, все в порядке. Но в масштабируемом проекте их количество может вырасти, и тогда разработчикам станет крайне сложно с ними справляться. Посмотрите, как быстро может разрастись такая структура:
Следующий код написан без использования паттерна Мост:
""" Code without using the bridge method We have a class with three attributes named as length, breadth, and height and three methods named as ProduceWithAPI1(), ProduceWithAPI2(), and expand().Out of these producing methods are implementation-specific as we have two production APIs""" class Cuboid: class ProducingAPI1: """Implementation Specific Implementation""" def produceCuboid(self, length, breadth, height): print(f'API1 is producing Cuboid with length = {length}, ' f' Breadth = {breadth} and Height = {height}') class ProducingAPI2: """Implementation Specific Implementation""" def produceCuboid(self, length, breadth, height): print(f'API2 is producing Cuboid with length = {length}, ' f' Breadth = {breadth} and Height = {height}') def __init__(self, length, breadth, height): """Initialize the necessary attributes""" self._length = length self._breadth = breadth self._height = height def produceWithAPI1(self): """Implementation specific Abstraction""" objectAPIone = self.ProducingAPI1() objectAPIone.produceCuboid(self._length, self._breadth, self._height) def producewithAPI2(self): """Implementation specific Abstraction""" objectAPItwo = self.
ProducingAPI2() objectAPItwo.produceCuboid(self._length, self._breadth, self._height) def expand(self, times): """Implementation independent Abstraction""" self._length = self._length * times self._breadth = self._breadth * times self._height = self._height * times # Instantiate a Cubiod cuboid1 = Cuboid(1, 2, 3) # Draw it using APIone cuboid1.produceWithAPI1() # Instantiate another Cuboid cuboid2 = Cuboid(19, 20, 21) # Draw it using APItwo cuboid2.producewithAPI2()
Решение проблемы при помощи паттерна Мост
Теперь давайте посмотрим на решение вышеуказанной проблемы с использованием паттерна Мост. Данный паттерн является одним из лучших решений для такого рода проблем.
Наша основная задача — разделить код абстракции и реализации. В результате мы получим следующую структуру:
Следующий код написан с использованием паттерна Мост:
"""Code implemented with Bridge Method.We have a Cuboid class having three attributes named as length, breadth, and height and three methods named as produceWithAPIOne(), produceWithAPItwo(), and expand(). Our purpose is to separate out implementation specific abstraction from implementation-independent abstraction""" class ProducingAPI1: """Implementation specific Abstraction""" def produceCuboid(self, length, breadth, height): print(f'API1 is producing Cuboid with length = {length}, ' f' Breadth = {breadth} and Height = {height}') class ProducingAPI2: """Implementation specific Abstraction""" def produceCuboid(self, length, breadth, height): print(f'API2 is producing Cuboid with length = {length}, ' f' Breadth = {breadth} and Height = {height}') class Cuboid: def __init__(self, length, breadth, height, producingAPI): """Initialize the necessary attributes Implementation independent Abstraction""" self._length = length self._breadth = breadth self._height = height self._producingAPI = producingAPI def produce(self): """Implementation specific Abstraction""" self.
_producingAPI.produceCuboid(self._length, self._breadth, self._height) def expand(self, times): """Implementation independent Abstraction""" self._length = self._length * times self._breadth = self._breadth * times self._height = self._height * times """Instantiate a cuboid and pass to it an object of ProducingAPIone""" cuboid1 = Cuboid(1, 2, 3, ProducingAPI1()) cuboid1.produce() cuboid2 = Cuboid(19, 19, 19, ProducingAPI2()) cuboid2.produce()
UML-диаграмма паттерна Мост
Вот так будет выглядеть UML-диаграмма паттерна Мост:
Преимущества применения паттерна Мост
- Принцип единой ответственности. Паттерн Мост четко следует принципу единой ответственности: он отделяет абстракцию от ее реализации, чтобы они могли видоизменяться независимо друг от друга.
- Принцип открытости/закрытости. Данный паттерн не нарушает принцип открытости/закрытости. Поскольку абстракция и реализация не зависят друг от друга, мы в любой момент можем ввести новые абстракции и реализации.
- Кроссплатформенность. Паттерн Мост можно легко использовать для реализации независимых от платформы функций и создания кроссплатформенных приложений.
Недостатки применения паттерна Мост
- Сложность. При использовании Моста наш код может усложниться, потому что мы вводим новые классы абстракций и реализаций.
- Снижение производительности. Применение Моста может немного ухудшить производительность.
- Интерфейсы только с одной реализацией. Если у нас ограниченное количество интерфейсов, то это не страшно. Но если у нас раздутый набор интерфейсов (абстракций) с минимальным количеством реализаций или вообще с одной реализацией, ими становится крайне трудно управлять.
Применение паттерна Мост
- Динамическое связывание. Обычно паттерн Мост используется для обеспечения динамического связывания реализации. Это означает, что мы можем вызывать метод во время выполнения, а не во время компиляции.
- Сопоставление классов. Данный паттерн также используется для сопоставления ортогональных иерархий классов.
- UI-окружение. Мост используется при определении очертаний пользовательского интерфейса.
Заключение
Итак, мы познакомились с паттерном проектирования Мост (Bridge). Теперь вы знаете, какую проблему он решает и как применяется. Кроме того, мы разобрали преимущества и недостатки данного паттерна.
Надеемся, данная статья была вам полезна! Успехов в написании кода!
Перевод статьи «Bridge Method – Python Design Patterns».
| Лидеры продаж ВИКФотоальбом дефектов основного металла Альбом радиографических снимков Документы
|
Мост
/ Шаблоны проектирования
/ Структурные модели
Intent
Bridge — это структурный шаблон проектирования, который позволяет разделить большой класс или набор тесно связанных классов на две отдельные иерархии — абстракцию и реализацию, — которые можно разрабатывать независимо друг от друга.
Проблема
Абстракция? Реализация? Звучит страшно? Сохраняйте спокойствие и давайте рассмотрим простой пример.
Допустим, у вас есть геометрический класс Shape
с парой подклассов: Circle
и Square
. Вы хотите расширить эту иерархию классов, включив в нее цвета, поэтому вы планируете создать подклассы формы Red
и Blue
. Однако, поскольку у вас уже есть два подкласса, вам нужно создать четыре комбинации классов, например 9.0022 BlueCircle и RedSquare
.
Количество комбинаций классов растет в геометрической прогрессии.
Добавление новых типов фигур и цветов в иерархию приведет к ее экспоненциальному росту. Например, чтобы добавить форму треугольника, вам нужно ввести два подкласса, по одному для каждого цвета. И после этого для добавления нового цвета потребуется создать три подкласса, по одному для каждого типа фигуры. Чем дальше, тем хуже становится.
Решение
Эта проблема возникает из-за того, что мы пытаемся расширить классы форм в двух независимых измерениях: по форме и по цвету. Это очень распространенная проблема с наследованием классов.
Шаблон Bridge пытается решить эту проблему, переключаясь с наследования на композицию объектов. Это означает, что вы извлекаете одно из измерений в отдельную иерархию классов, так что исходные классы будут ссылаться на объект новой иерархии, а не иметь все его состояние и поведение в одном классе.
Вы можете предотвратить взрыв иерархии классов, преобразовав ее в несколько связанных иерархий.
Следуя этому подходу, мы можем выделить код, связанный с цветом, в отдельный класс с двумя подклассами: Красный
и Синий
. Затем класс Shape
получает поле ссылки, указывающее на один из цветовых объектов. Теперь фигура может делегировать любую работу, связанную с цветом, связанному объекту цвета. Эта ссылка будет действовать как мост между классами
Shape
и Color
. Отныне добавление новых цветов не потребует изменения иерархии фигур, и наоборот.
Абстракция и реализация
В книге GoF представлены термины Абстракция и Реализация как часть определения моста. На мой взгляд, термины звучат слишком академично и делают схему более сложной, чем она есть на самом деле. Прочитав простой пример с формами и цветами, давайте расшифруем значение страшных слов из книги GoF.
Абстракция (также называемая интерфейсом ) представляет собой уровень управления высокого уровня для некоторой сущности. Этот слой не должен выполнять какую-либо реальную работу сам по себе. Он должен делегировать работу 9Уровень реализации 0016 (также называемый платформой ).
Обратите внимание, что мы не говорим об интерфейсах или абстрактных классах из вашего языка программирования. Это не одно и то же.
Говоря о реальных приложениях, абстракция может быть представлена графическим пользовательским интерфейсом (GUI), а реализацией может быть лежащий в основе код операционной системы (API), который уровень GUI вызывает в ответ на действия пользователя.
Вообще говоря, вы можете расширять такое приложение в двух независимых направлениях:
- Иметь несколько различных графических интерфейсов (например, адаптированных для постоянных клиентов или администраторов).
- Поддержка нескольких различных API (например, чтобы иметь возможность запускать приложение в Windows, Linux и macOS).
В худшем случае это приложение может выглядеть как гигантская чаша для спагетти, где сотни условных выражений соединяют различные типы графического интерфейса пользователя с различными API по всему коду.
Внести даже простое изменение в монолитную кодовую базу довольно сложно, потому что вы должны очень хорошо понимать все это . Вносить изменения в небольшие, четко определенные модули намного проще.
Вы можете навести порядок в этом хаосе, выделив код, относящийся к конкретным комбинациям интерфейса и платформы, в отдельные классы. Однако вскоре вы обнаружите, что существует лотов этих классов. Иерархия классов будет расти в геометрической прогрессии, потому что добавление нового графического интерфейса или поддержка другого API потребует создания все большего количества классов.
Попробуем решить эту проблему с помощью паттерна Bridge. Предлагается разделить классы на две иерархии:
- Абстракция: слой графического интерфейса приложения.
- Реализация: API операционных систем.
Один из способов структурирования кроссплатформенного приложения.
Объект абстракции управляет внешним видом приложения, делегируя фактическую работу связанному объекту реализации. Различные реализации взаимозаменяемы, если они следуют общему интерфейсу, что позволяет одному и тому же графическому интерфейсу работать под Windows и Linux.
В результате вы можете изменять классы графического интерфейса, не касаясь классов, связанных с API. Более того, добавление поддержки другой операционной системы требует только создания подкласса в иерархии реализации.
Структура
Abstraction обеспечивает высокоуровневую логику управления. Он полагается на объект реализации для выполнения фактической низкоуровневой работы.
Реализация объявляет интерфейс, общий для всех конкретных реализаций. Абстракция может взаимодействовать с объектом реализации только через объявленные здесь методы.
В абстракции могут быть перечислены те же методы, что и в реализации, но обычно абстракция объявляет некоторые сложные поведения, основанные на большом количестве примитивных операций, объявленных реализацией.
Конкретные реализации содержат код для конкретной платформы.
Усовершенствованные абстракции предоставляют варианты логики управления.
Как и их родитель, они работают с разными реализациями через общий интерфейс реализации.
Обычно Клиент заинтересован только в работе с абстракцией. Однако работа клиента заключается в том, чтобы связать объект абстракции с одним из объектов реализации.
Псевдокод
В этом примере показано, как шаблон Bridge может помочь разделить монолитный код приложения, управляющего устройствами и их пультами дистанционного управления. Классы Device
действуют как реализация, тогда как классы Remote
действуют как абстракция.
Исходная иерархия классов разделена на две части: устройства и пульты дистанционного управления.
Базовый класс дистанционного управления объявляет поле ссылки, которое связывает его с объектом устройства. Все пульты работают с устройствами через общий интерфейс устройства, что позволяет одному и тому же пульту поддерживать несколько типов устройств.
Вы можете разрабатывать классы дистанционного управления независимо от классов устройств. Все, что нужно, это создать новый удаленный подкласс. Например, базовый пульт дистанционного управления может иметь только две кнопки, но вы можете расширить его дополнительными функциями, такими как дополнительная батарея или сенсорный экран.
Клиентский код связывает желаемый тип дистанционного управления с конкретным объектом устройства через конструктор пульта.
// "Абстракция" определяет интерфейс для "управления"
// часть двух иерархий классов. Он поддерживает ссылку
// к объекту иерархии "реализация" и делегатам
// вся реальная работа с этим объектом.
класс RemoteControl есть
защищенное полевое устройство: Устройство
конструктор RemoteControl(device: Device)
это.устройство = устройство
метод togglePower() есть
если (device.isEnabled()), то
устройство.отключить()
еще
устройство.включить()
метод VolumeDown()
устройство.
setVolume(device.getVolume() - 10)
метод VolumeUp() есть
устройство.setVolume(device.getVolume() + 10)
метод channelDown() есть
устройство.setChannel(устройство.getChannel() - 1)
метод channelUp() есть
устройство.setChannel(устройство.getChannel() + 1)
// Вы можете расширять классы из иерархии абстракций
// независимо от классов устройств.
класс AdvancedRemoteControl расширяет RemoteControl
метод немой ()
устройство.setVolume(0)
// Интерфейс "реализации" объявляет методы, общие для всех
// конкретные классы реализации. Он не должен совпадать с
// интерфейс абстракции. Фактически, два интерфейса могут быть
// совсем другое. Обычно интерфейс реализации
// обеспечивает только примитивные операции, а абстракция
// определяет операции более высокого уровня на основе этих примитивов.
Интерфейс Устройство
метод включен()
включить метод()
отключить метод()
метод получить объем ()
метод setVolume (в процентах)
метод получитьканал()
метод setChannel (канал)
// Все устройства используют один и тот же интерфейс.
класс Tv реализует устройство
// ...
класс Radio реализует устройство
// ...
// Где-то в клиентском коде.
телевизор = новый телевизор()
пульт = новый пульт дистанционного управления (телевизор)
удаленный.togglePower()
радио = новое радио()
удаленный = новый AdvancedRemoteControl (радио)
Применимость
Используйте шаблон Мост, когда вы хотите разделить и организовать монолитный класс, который имеет несколько вариантов некоторой функциональности (например, если класс может работать с различными серверами баз данных).
Чем больше становится класс, тем сложнее понять, как он работает, и тем больше времени требуется для внесения изменений. Изменения, внесенные в один из вариантов функциональности, могут потребовать внесения изменений во всем классе, что часто приводит к ошибкам или неустранению некоторых критических побочных эффектов.
Шаблон Bridge позволяет разделить монолитный класс на несколько иерархий классов. После этого вы можете изменять классы в каждой иерархии независимо от классов в других. Такой подход упрощает обслуживание кода и сводит к минимуму риск взлома существующего кода.
Используйте шаблон, когда вам нужно расширить класс в нескольких ортогональных (независимых) измерениях.
Мост предлагает выделить отдельную иерархию классов для каждого измерения. Исходный класс делегирует связанную работу объектам, принадлежащим этим иерархиям, вместо того, чтобы делать все самостоятельно.
Используйте мост, если вам нужно иметь возможность переключать реализации во время выполнения.
Хотя это и необязательно, шаблон Bridge позволяет заменить объект реализации внутри абстракции. Это так же просто, как присвоить новое значение полю.
Кстати, именно этот последний пункт является основной причиной того, что так много людей путают Мост с паттерном Стратегия. Помните, что шаблон — это больше, чем просто определенный способ структурирования ваших классов. Он также может сообщать о намерениях и решаемой проблеме.
Как реализовать
Определите ортогональные размеры в ваших классах. Этими независимыми понятиями могут быть: абстракция/платформа, домен/инфраструктура, внешний/внутренний интерфейс или интерфейс/реализация.
Посмотрите, какие операции нужны клиенту, и определите их в базовом классе абстракции.
Определить операции, доступные на всех платформах. Объявите те, которые нужны абстракции, в общем интерфейсе реализации.
Для всех платформ в вашем домене создайте конкретные классы реализации, но убедитесь, что все они соответствуют интерфейсу реализации.
Внутри класса абстракции добавьте поле ссылки для типа реализации. Абстракция делегирует большую часть работы объекту реализации, на который ссылается это поле.
Если у вас есть несколько вариантов высокоуровневой логики, создайте уточненные абстракции для каждого варианта, расширив базовый класс абстракции.
Код клиента должен передать объект реализации конструктору абстракции, чтобы связать один объект с другим. После этого клиент может забыть о реализации и работать только с объектом абстракции.
Плюсы и минусы
- Вы можете создавать независимые от платформы классы и приложения.
- Код клиента работает с абстракциями высокого уровня. Он не подвергается воздействию деталей платформы.
- Открытый/Закрытый принцип . Вы можете вводить новые абстракции и реализации независимо друг от друга.
- Принцип единой ответственности . Вы можете сосредоточиться на логике высокого уровня в абстракции и на деталях платформы в реализации.
- Вы можете усложнить код, применив шаблон к очень связанному классу.
Связь с другими шаблонами
Bridge обычно разрабатывается заранее, что позволяет разрабатывать части приложения независимо друг от друга.
С другой стороны, адаптер обычно используется с существующим приложением, чтобы некоторые несовместимые в противном случае классы хорошо работали вместе.
Мост, Состояние, Стратегия (и в некоторой степени Адаптер) имеют очень похожую структуру. Действительно, все эти шаблоны основаны на композиции, которая делегирует работу другим объектам. Однако все они решают разные задачи. Шаблон — это не просто рецепт структурирования вашего кода определенным образом. Он также может сообщить другим разработчикам о проблеме, которую решает шаблон.
Вы можете использовать Абстрактную Фабрику вместе с Мостом. Это объединение полезно, когда некоторые абстракции, определенные Bridge , могут работать только с конкретными реализациями. В этом случае Abstract Factory может инкапсулировать эти отношения и скрыть сложность от клиентского кода.
Вы можете комбинировать Builder с Bridge: класс-директор играет роль абстракции, а разные билдеры выступают в роли реализаций.
Примеры кодов
Мост в Python/шаблоны проектирования
РОЖДЕСТВЕНСКАЯ РАСПРОДАЖА ИДЕТ!
/ Шаблоны проектирования
/ Мост
/ Питон
Мост — это структурный шаблон проектирования, который делит бизнес-логику или огромный класс на отдельные иерархии классов, которые можно разрабатывать независимо.
Одна из этих иерархий (часто называемая Абстракцией) получит ссылку на объект второй иерархии (Реализация). Абстракция сможет делегировать некоторые (иногда большинство) своих вызовов объекту реализации. Поскольку все реализации будут иметь общий интерфейс, внутри абстракции они будут взаимозаменяемы.
Подробнее о мосте
Сложность:
Популярность:
Примеры использования: Шаблон моста особенно полезен при работе с кроссплатформенными приложениями, поддержке нескольких типов серверов баз данных или работе с несколькими поставщиками API определенного типа (например, облачными платформами, социальными сетями и т. д.)
Идентификация: Мост можно узнать по четкому различию между некоторым контролирующим лицом и несколькими различными платформами, на которые он опирается.
Концептуальный пример
Этот пример иллюстрирует структуру шаблона проектирования Bridge . Он фокусируется на ответах на эти вопросы:
- Из каких классов он состоит?
- Какие роли играют эти классы?
- Каким образом связаны элементы узора?
main.py: Концептуальный пример
из __future__ импортировать аннотации
из abc импортировать ABC, abstractmethod
Абстракция класса:
"""
Абстракция определяет интерфейс для «управляющей» части двух
иерархии классов. Он поддерживает ссылку на объект
Иерархия реализации и делегирует всю реальную работу этому объекту.
"""
def __init__(self, реализация: Реализация) -> Нет:
самореализация = реализация
операция защиты (самостоятельная) -> ул:
return (f"Абстракция: базовая операция с:\n"
е"{самостоятельная реализация.
операция_реализация()}")
класс Расширенная Абстракция (Абстракция):
"""
Вы можете расширить абстракцию без изменения классов реализации.
"""
операция защиты (самостоятельная) -> ул:
return (f"ExtendedAbstraction: Расширенная операция с:\n"
е"{самостоятельная реализация.операция_реализация()}")
Реализация класса (ABC):
"""
Реализация определяет интерфейс для всех классов реализации. Это
не обязательно должен соответствовать интерфейсу Абстракции. На самом деле, два
интерфейсы могут быть совершенно разными. Обычно интерфейс реализации
обеспечивает только примитивные операции, в то время как Абстракция определяет более сложные операции.
операции уровня, основанные на этих примитивах.
"""
@абстрактный метод
def operation_implementation(self) -> str:
проходят
"""
Каждая конкретная реализация соответствует определенной платформе и реализует
интерфейс реализации с использованием API этой платформы.
"""
класс ConcreteImplementationA (реализация):
def operation_implementation(self) -> str:
return «ConcreteImplementationA: Вот результат на платформе A».