Кто-нибудь когда-нибудь использовал Bridge Pattern в реальных приложениях? Если да, то как вы это использовали? Это я, или это просто шаблон адаптера с небольшой инъекцией зависимости, добавленной в микс? Действительно ли он заслуживает собственного образца?
Когда вы используете паттерн «Мостик»? Чем он отличается от шаблона адаптера?
Ответы (12)
Классический пример паттерна Мост используется при определении фигур в среде пользовательского интерфейса (см. запись в Википедии о паттерне моста). Шаблон "Мост" - это составная часть шаблон и Шаблоны стратегии.
Это обычное представление о некоторых аспектах паттерна «Адаптер» в паттерне «Мост». Однако, если процитировать эту статью:
На первый взгляд, шаблон «Мост» очень похож на шаблон «Адаптер» в том смысле, что класс используется для преобразования одного вида интерфейса в другой. Однако цель шаблона адаптера - сделать так, чтобы интерфейсы одного или нескольких классов выглядели так же, как интерфейс конкретного класса. Шаблон «Мост» предназначен для отделения интерфейса класса от его реализации, поэтому вы можете изменить или заменить реализацию без изменения клиентского кода.
Есть комбинация Федерико и Ответы Джона.
Когда:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Выполните рефакторинг для:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
Паттерн «Мост» - это применение старого совета «предпочитать композицию наследованию». Это становится удобным, когда вы должны создавать подклассы разных времен ортогонально друг другу. Скажем, вы должны реализовать иерархию цветных фигур. Вы бы не стали подклассом Shape с Rectangle и Circle, а затем подклассом Rectangle с RedRectangle, BlueRectangle и GreenRectangle и то же самое для Circle, не так ли? Вы бы предпочли сказать, что каждая фигура имеет цвет, и реализовать иерархию цветов, и это шаблон моста. Ну, я бы не стал реализовывать «иерархию цветов», но вы поняли ...
Когда:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Выполните рефакторинг для:
A N
/ \ / \
Aa(N) Ab(N) 1 2
По моему опыту, мост - довольно часто повторяющийся шаблон, потому что это решение, когда в домене есть два ортогональных измерения. Например. формы и методы рисования, поведения и платформы, форматы файлов и сериализаторы и так далее.
И совет: всегда думайте о шаблонах проектирования с концептуальной точки зрения, а не с точки зрения реализации. С правильной точки зрения, Bridge нельзя путать с адаптером, потому что они решают другую проблему, а композиция превосходит наследование не ради себя, а потому, что позволяет обрабатывать ортогональные проблемы по отдельности.
Адаптер и мост, безусловно, связаны между собой, и разница между ними невелика. Вероятно, что некоторые люди, которые думают, что используют один из этих шаблонов, на самом деле используют другой шаблон.
Я видел объяснение, что адаптер используется, когда вы пытаетесь объединить интерфейсы некоторых несовместимых классов, которые уже существуют. Адаптер функционирует как своего рода переводчик для реализаций, которые можно считать устаревшими.
В то время как шаблон Bridge используется для кода, который, скорее всего, будет новым. Вы проектируете мост, чтобы предоставить абстрактный интерфейс для реализации, которая должна изменяться, но вы также определяете интерфейс этих классов реализации.
Драйверы устройств - это часто цитируемый пример Bridge, но я бы сказал, что это мост, если вы определяете спецификацию интерфейса для поставщиков устройств, но это адаптер, если вы берете существующие драйверы устройств и создаете класс-оболочку для обеспечить единый интерфейс.
Итак, с точки зрения кода эти два шаблона очень похожи. С точки зрения бизнеса они разные.
См. Также http://c2.com/cgi/wiki?BridgePattern.
Назначение Моста и Адаптера различно, и нам нужны оба шаблона отдельно.
Образец моста:
- Это структурный образец
- Абстракция и реализация не связаны во время компиляции
- Абстракция и реализация - оба могут меняться без влияния на клиента
- Использует композицию вместо наследования.
Используйте паттерн Мост, когда:
- Вы хотите привязку реализации во время выполнения,
- У вас есть множество классов, являющихся результатом связанного интерфейса и множества реализаций,
- Вы хотите поделиться реализацией между несколькими объектами,
- Вам необходимо отобразить ортогональные иерархии классов.
Ответ @ John Sonmez ясно показывает эффективность шаблона моста в уменьшении иерархии классов.
Вы можете обратиться к приведенной ниже ссылке на документацию, чтобы лучше понять шаблон моста с примером кода.
Шаблон адаптера:
- Он позволяет двум несвязанным интерфейсам работать вместе через разные объекты, возможно, играя одну и ту же роль.
- Изменяет оригинальный интерфейс.
Ключевые отличия:
- Адаптер заставляет вещи работать после того, как они спроектированы; Bridge заставляет их работать раньше, чем они есть.
- Мост разработан заранее, чтобы позволить абстракции и реализации варьироваться независимо. Адаптер модернизирован для совместной работы несвязанных классов.
- Цель: Адаптер позволяет двум несвязанным интерфейсам работать вместе. Мост позволяет изменять абстракцию и реализацию независимо.
Связанный вопрос SE с диаграммой UML и рабочим кодом:
Разница между шаблоном моста и шаблоном адаптера
Полезные статьи:
Sourcemaking Bridge, статья о шаблоне
адаптер создания исходного кода, статья о шаблоне
Шаблон статьи journaldev Bridge
РЕДАКТИРОВАТЬ:
Образец моста в реальном мире (в соответствии с предложением meta.stackoverflow.com, в этом посте включен пример сайта документации, поскольку документация скоро закроется)
Шаблон моста отделяет абстракцию от реализации, так что оба могут варьироваться независимо. Это было достигнуто с помощью композиции, а не наследования.
Образец моста UML из Википедии:
У вас есть четыре компонента в этом шаблоне.
Abstraction
: определяет интерфейс
RefinedAbstraction
: Реализует абстракцию:
Implementor
: Он определяет интерфейс для реализации
ConcreteImplementor
: Реализует интерфейс разработчика.
The crux of Bridge pattern :
Две ортогональные иерархии классов с использованием композиции (без наследования). Иерархия абстракций и иерархия реализации могут изменяться независимо. Реализация никогда не ссылается на абстракцию. Абстракция содержит интерфейс реализации в качестве члена (через композицию). Эта композиция снижает еще один уровень иерархии наследования.
Реальный вариант использования слова:
Разрешить разным автомобилям использовать как ручную, так и автоматическую коробку передач.
Пример кода:
/* Implementor interface*/
interface Gear{
void handleGear();
}
/* Concrete Implementor - 1 */
class ManualGear implements Gear{
public void handleGear(){
System.out.println("Manual gear");
}
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
public void handleGear(){
System.out.println("Auto gear");
}
}
/* Abstraction (abstract class) */
abstract class Vehicle {
Gear gear;
public Vehicle(Gear gear){
this.gear = gear;
}
abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
public Car(Gear gear){
super(gear);
// initialize various other Car components to make the car
}
public void addGear(){
System.out.print("Car handles ");
gear.handleGear();
}
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
public Truck(Gear gear){
super(gear);
// initialize various other Truck components to make the car
}
public void addGear(){
System.out.print("Truck handles " );
gear.handleGear();
}
}
/* Client program */
public class BridgeDemo {
public static void main(String args[]){
Gear gear = new ManualGear();
Vehicle vehicle = new Car(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Car(gear);
vehicle.addGear();
gear = new ManualGear();
vehicle = new Truck(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Truck(gear);
vehicle.addGear();
}
}
выход:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Объяснение:
Vehicle
- это абстракция.Car
иTruck
- две конкретные реализацииVehicle
.Vehicle
определяет абстрактный метод:addGear()
.Gear
- интерфейс разработчикаManualGear
иAutoGear
- две реализацииGear
Vehicle
содержитimplementor
интерфейс, а не реализует интерфейс.Compositon
интерфейса разработчика является сутью этого шаблона: Он позволяет абстракции и реализации варьироваться независимо.Car
иTruck
определяют реализацию (переопределенную абстракцию) для абстракции:addGear()
: Он содержитGear
- ЛибоManual
, либоAuto
Варианты использования шаблона "Мост":
- Абстракция и Реализация могут изменяться независимо друг от друга и не связаны во время компиляции.
- Сопоставьте ортогональные иерархии - одна для абстракции и одна для реализации.
Я использовал схему моста в работе. Я программирую на C ++, где это часто называют идиомой PIMPL (указатель на реализацию). Это выглядит так:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
В этом примере class A
содержит интерфейс, а class Aimpl
содержит реализацию.
Одно из применений этого шаблона - раскрытие только некоторых общедоступных членов класса реализации, но не других. В этом примере через открытый интерфейс A
можно вызывать только Aimpl::foo()
, но не Aimpl::bar()
Еще одно преимущество состоит в том, что вы можете определить Aimpl
в отдельном файле заголовка, который не нужно включать пользователям A
. Все, что вам нужно сделать, это использовать предварительное объявление Aimpl
до определения A
и переместить определения всех функций-членов, ссылающихся на pImpl
, в файл .cpp. Это дает вам возможность сохранить конфиденциальность заголовка Aimpl
и сократить время компиляции.
Чтобы поместить пример формы в код:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class IColor
{
public:
virtual string Color() = 0;
};
class RedColor: public IColor
{
public:
string Color()
{
return "of Red Color";
}
};
class BlueColor: public IColor
{
public:
string Color()
{
return "of Blue Color";
}
};
class IShape
{
public:
virtual string Draw() = 0;
};
class Circle: public IShape
{
IColor* impl;
public:
Circle(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Circle "+ impl->Color();
}
};
class Square: public IShape
{
IColor* impl;
public:
Square(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Square "+ impl->Color();;
}
};
int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();
IShape* sq = new Square(red);
IShape* cr = new Circle(blue);
cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();
delete red;
delete blue;
return 1;
}
Результат:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
Обратите внимание на легкость, с которой новые цвета и формы могут быть добавлены в систему, не приводя к взрывному росту подклассов из-за перестановок.
Вы работаете в страховой компании, где разрабатываете приложение для рабочего процесса, которое управляет различными задачами: бухгалтерский учет, договор, претензии. Это абстракция. Что касается реализации, вы должны иметь возможность создавать задачи из разных источников: электронная почта, факс, электронные сообщения.
Вы начинаете свой дизайн с этих классов:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Теперь, поскольку каждый источник должен обрабатываться определенным образом, вы решаете специализировать каждый тип задачи:
public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}
public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}
public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}
У вас осталось 13 классов. Добавление типа задачи или типа источника становится сложной задачей. Использование шаблона моста упрощает обслуживание, отделяя задачу (абстракцию) от источника (что является проблемой реализации):
// Source
public class Source {
public string GetSender();
public string GetMessage();
public string GetContractReference();
(...)
}
public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}
// Task
public class Task {
public Task(Source source);
(...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Добавить тип задачи или источник стало намного проще.
Примечание. Большинство разработчиков не создают иерархию из 13 классов заранее, чтобы решить эту проблему. Однако в реальной жизни вы можете не знать заранее количество источников и типов задач; если у вас есть только один источник и два типа задач, вы, вероятно, не будете отделять задачу от источника. Затем общая сложность растет по мере добавления новых источников и типов задач. В какой-то момент вы проведете рефакторинг и, чаще всего, получите решение, подобное мосту.
для меня я думаю об этом как о механизме, в котором вы можете поменять местами интерфейсы. В реальном мире у вас может быть класс, который может использовать более одного интерфейса, Bridge позволяет вам менять местами.