Паттерн Мост (Bridge)

Название и классификация паттерна

Мост — паттерн, структурирующий объекты.

Назначение паттерна Bridge

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

В системе могут существовать классы, отношения между которыми строятся в соответствии со следующей объектно-ориентированной иерархией: абстрактный базовый класс объявляет интерфейс, а конкретные подклассы реализуют его нужным образом. Такой подход является стандартным в ООП, однако ему свойственны следующие недостатки:

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

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

Стандартный подход на основе полиморфизма использует следующую иерархию классов (рис. 42).

Иерархия классов Логгера на основе полиморфизма

Рис. 42. Иерархия классов Логгера на основе полиморфизма

Число родственных подклассов в системе равно 6. Добавление еще одного вида логгера увеличит его до 8, двух — до 10 и т. д. Система становится трудно управляемой.

Устранить указанные недостатки можно применением паттерна Bridge.

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

Использование паттерна Мост целесообразно в следующих случаях:

  • • необходимо избежать постоянной привязки абстракции к реализации. Так, например, бывает, когда реализацию необходимо выбирать во время выполнения программы;
  • • и абстракции, и реализации должны расширяться новыми подклассами. В таком случае паттерн Мост позволяет комбинировать разные абстракции и реализации и изменять их независимо;
  • • изменения в реализации абстракции не должны сказываться на клиентах, т. е. клиентский код не должен перекомпилироваться;
  • (только для C++!) необходимо полностью скрыть от клиентов реализацию абстракции. В C++ представление класса видно через его интерфейс;
  • • число классов начинает быстро расти, как видно из рис. 43. Это признак того, что иерархию следует разделить на части;
  • • требуется разделить одну реализацию между несколькими объектами (быть может, применяя подсчет ссылок), и этот факт необходимо скрыть от клиента.

Описание паттерна Bridge

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

Первая иерархия определяет интерфейс абстракции, доступный пользователю. Для случая проектируемого нами логгера абстрактный базовый класс Logger мог бы объявить интерфейс метода log() для вывода сообщений. Класс Logger также содержит указатель на реализацию pimpl, который инициализируется должным образом при создании логгера конкретного типа. Этот указатель используется для перенаправления пользовательских запросов в реализацию. Заметим, в общем случае подклассы ConsoleLogger, FileLogger и Socket Logger могут расширять интерфейс класса Logger.

Все детали реализации, связанные с особенностями среды, скрываются во второй иерархии. Базовый класс Loggerlmpl объявляет интерфейс операций, предназначенных для отправки сообщений на экран, файл и удаленный компьютер, а подклассы ST_LoggerImpl и MT_LoggerImpl его реализуют для однопоточной и многопоточной среды соответственно. В общем случае интерфейс Loggerlmpl необязательно должен в точности соответствовать интерфейсу абстракции. Часто он выглядит как набор низкоуровневых примитивов (рис. 43).

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

UML-диаграмма классов паттерна Bridge

Рис. 43. UML-диаграмма классов паттерна Bridge

на Bridge также позволяет сократить общее число подклассов в системе, что делает ее более простой в поддержке.

Структура паттерна Bridge

Структура паттерна Мост приведена на рис. 44.

imp->operationlmp(); ^

UML-диаграмма классов паттерна Bridge

Рис. 44. UML-диаграмма классов паттерна Bridge

Участники

Abstraction (Logger) — абстракция: определяет интерфейс абстракции; хранит ссылку на объект типа Implementor.

RefinedAbstraction (ConsoleLogger, SocketLogger, File Logger) — уточненная абстракция: расширяет интерфейс, определенный абстракцией Abstraction.

Implementor (Loggerlmp) — реализатор: определяет интерфейс для классов реализации. Он не обязан точно соответствовать интерфейсу класса Abstraction. На самом деле оба интерфейса могут быть совершенно различны. Обычно интерфейс класса Implementor предоставляет только примитивные операции, а класс Abstraction определяет операции более высокого уровня, базирующиеся на этих примитивах.

Concretelmplementor (STLoggerlmp, MT Loggerlmp) — конкретный реализатор: содержит конкретную реализацию интерфейса класса Implementor.

Отношения

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

Результаты

Результаты применения паттерна Мост таковы.

Отделение реализации от интерфейса. Реализация больше не имеет постоянной привязки к интерфейсу. Реализацию абстракции можно конфигурировать во время выполнения. Объект может даже динамически изменять свою реализацию.

Разделение классов Abstraction и Implementor устраняет также зависимости от реализации, устанавливаемые на этапе компиляции. Чтобы изменить класс реализации, вовсе не обязательно перекомпилировать класс Abstraction и его клиентов. Это свойство особенно важно, если необходимо обеспечить двоичную совместимость между разными версиями библиотеки классов.

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

Повышение степени расширяемости. Можно расширять независимо иерархии классов Abstraction и Implementor.

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

Разделение реализаторов. Паттерны Bridge и Adapter имеют схожую структуру, однако цели их использования различны. Если паттерн Adapter применяют для адаптации уже существующих классов в систему, то паттерн Bridge используется на стадии ее проектирования.

Реализация паттерна Bridge

Приведем реализацию логгера с применением паттерна Bridge.

// Logger.h - Абстракция

#include

// Опережающее объявление

class Logger!mpl;

class Logger

{

public:

Logger( Loggerlmpl* p); virtual ~Logger(); virtual void log( string & str) = 0; protected:

Loggerlmpl * pimpl;

};

class Console Logger: public Logger

{

риЬНс:

Сошо1еЬег(); уоМ 1о§( вТги^ & 8Тг);

};

с1а$8 РПеЬег: риЬНс Ьс^ег

{

риЬНс:

РПеЬо§?ег( вТпгщ & Ше_пате ); уЫс! к^( вТПГЩ & 81г); рпуаТе: зТпгщ Ше;

};

с1а88 Боскет Ьовяег: риЬНс Ьег

{

риЬНс:

БоскеТРо??ег( вТпгщ & гешоТе_Ьо8Т, тТ гешоТе_рш1); уоМ 1о§( вТПГЩ & 8ТГ ); рпуаТе: вТпгщ Но8Т; тТ роЛ;

};

// Ьо§?ег.срр — Абстракция #тс1ис1е 'Ъег.Н"

#тс1ис1е "LoggerImpl.li"

Logger::Logger( LoggerImpl* р ): р1тр1(р)

{}

Logger::~Logger()

{

с1е1еТе р1шр1;

}

ConsoleLogger::ConsoleLogger(): Logger( #1ГбеГ МТ

new MT_LoggerImpl()

#е18е

пеу ST_LoggerImpl()

#епсНГ

)

void ConsoleLogger::log( string & str)

{

pimpl->console_log( str);

}

File Logger:: File Logger( string & file_name ): Logger( #ifdef MT

new MT_LoggerImpl()

#else

new ST_LoggerImpl()

#endif

), file(file_name)

void File Logger: :log( string & str)

{

pimpl->file_log( File, str);

Socket Logger:: Socket Logger( string & remote_host,

int remote_port): Logger(

#ifdef MT

new MT_LoggerImpl()

#else

new ST_LoggerImpl()

#endif

), host(remote_host), port(remote_port)

void SocketLogger::log( string & str)

{

pimpl->socket_log( host, port, str);

}

// Loggerlmpl.h — Реализация #include

class Loggerlmpl

{

public:

virtual -LoggerlmpK) {}

virtual void console_log( string & str) = 0;

virtual void file_log(

string & file, string & str) = 0; virtual void socket_log(

tring & host, int port, string & str) = 0;

};

class ST_LoggerImpl: public Loggerlmpl

{

public:

void console_log( string & str);

void file_log ( string & file, string & str);

void socket_log (

string & host, int port, string & str);

};

class MT_LoggerImpl: public Loggerlmpl

{

public:

void console_log( string & str);

void file_log ( string & file, string & str);

void socket_log (

string & host, int port, string & str);

};

// Loggerlmpl.cpp — Реализация #include

#include "Loggerlmpl.h"

void ST_LoggerImpl::console_log( string & str)

{

cout << "Single-threaded console logger" << endl;

}

void ST_LoggerImpl::file_log( string & file, string & str)

{

cout << "Single-threaded file logger" << endl;

}

void ST_LoggerImpl::socket_log(

string & host, int port, string & str)

{

cout << "Single-threaded socket logger" << endl;

};

void MT_LoggerImpl::console_log( string & str)

{

cout << "Multithreaded console logger" << endl;

}

void MT_LoggerImpl::file_log( string & file, string & str)

{

cout << "Multithreaded file logger" << endl;

}

void MT_LoggerImpl::socket_log(

string & host, int port, string & str )

{

cout << "Multithreaded socket logger" << endl;

}

// Main.cpp #include

#include "Logger.h"

int main()

{

Logger * p = new FileLogger( string("log.txt")); p->log( string("message")); delete p; return 0;

}

Отметим несколько важных моментов приведенной реализации паттерна Bridge:

  • • при модификации реализации клиентский код перекомпилировать не нужно. Использование в абстракции указателя на реализацию (идиома pimpl) позволяет заменить в файле Logger.h включение include «Loggerlmpl.h» на опережающее объявление class Loggerlmpl. Такой прием снимает зависимость времени компиляции файла Logger.h (и, соответственно, использующих его файлов клиента) от файла Loggerlmpl.h;
  • • пользователь класса Logger не видит никаких деталей его реализации.

Результаты применения паттерна Bridge Достоинства паттерна Bridge

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

Возможность динамического изменения реализации в процессе выполнения программы.

Паттерн Bridge полностью скрывает реализацию от клиента. В случае модификации реализации пользовательский код не требует перекомпиляции.

Родственные паттерны

Паттерн Абстрактная фабрика может создать и сконфигурировать Мост. Для обеспечения совместной работы не связанных между собой классов прежде всего предназначен паттерн Адаптер. Обычно он применяется в уже готовых системах. Мост же участвует в проекте с самого начала и призван поддержать возможность независимого изменения абстракций и их реализаций.

 
< Пред   СОДЕРЖАНИЕ     След >