Pull to refresh

Паттерн проектирования «Мост» / «Bridge»

Reading time5 min
Views49K
upd: Изменил диаграмму и код. Расширил пример и описание.

Почитать описание других паттернов.

Предыстория


Вернувшись домой, после непродолжительных посиделок у старого друга, я обнаружил, что оставил у него свой мобильный телефон, а вместе с тем и единственный в квартире будильник. Ситуация осложнялась тем, что завтра в 8:00 надо было быть на работе. Вариант вернуться за мобильником в 11 часов вечера я даже не рассматривал. И первое, что пришло мне на ум — написать свой будильник, причем с применением паттерна «Мост», который мне и без того надо было реализовать в рамках спецкурса. Как говорится, двух зайцев… Я думаю, не стоит пояснять что лег спать я под утро, но довольный собой. А утром, ровно в 7:00 меня победоносно разбудил мой bridge-будильник, весело наигрывая мотив из TBBT.

Как я до такого докатился, читайте под хаброкатом.

Проблема


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

Описание


Невозможно представить объектно-ориентированное программирование и анализ без понятия абстракции и реализации. К тому же, современные языки программирования содержат специализированные конструкции для описания абстракций и реализаций. Любой адекватный разработчик, использует абстракции при решении тех или иных задач. Например java.util.List — абстракция списка, java.util.ArrayList — реализация данного списка на базе массивов, java.util.LinkedList — реализация на базе связных списков. При этом, единственный применяемый механизм, при описании абстракций — механизм наследования. Иными словами, реализации должны реализовывать (прошу прощения за тафтологию) интерфейс абстракции.

Однако, по мимо основного механизма описания абстракций — механизма наследования (реализации интерфейсов), существует и другой — применения паттерна «Мост».

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

Итак, отделяем абстракцию от реализации с помощью «Моста». Для этого, на все реализации заводим общий интерфейс, который они (реализации) будут имплементировать. В интерфейсе абстракции храним ссылку на интерфейс реализации. Теперь мы можем совершенно независимо модифицировать абстракцию — путем уточнения интерфейса абстракции и реализацию — путем имплементации интерфейса реализации. Единственное, на чем я хотел заострить внимание — проектирование интерфейсов абстракции и реализации. Как правило интерфейс реализации содержит простейшие методы, в то время как интерфейс абстракции методы более высокого уровня, реализация которых на самом деле является суперпозицией простейших методов из интерфейса реализации.

Практическая задача


Реализуем bridge-будильник :) Пусть интерфейс будильника — AlarmClock, умеющий запускаться (start), останавливаться (stop) и собственно будить (toWake), интерфейс реализации — AlarmClockImpl, описывающий два метода — зазвонить(ring) и сообщить (notify). Очевидно, что метод toWake будет поочередно вызывать два этих метода из реализации. Сначала — сообщит, о том, что «время пришло», затем и зазвонит. Соединим данные интерфейсы мостом (ссылка на AlarmClockImpl внутри AlarmClock). Теперь напишем уточнение AlarmClock — как «зависающего будильника» (LockupAlarmClock) и две реализации AlarmClockImpl — как будильника играющего MP3 посредством выполнения внешней системной команды (ShellMP3AlarmClock) и будильника играющего MP3, посредством системных библиотек (SystemMP3AlarmClock).

Диаграмма классов



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



Реализация


Реализации SystemMP3AlarmClock, в приведенном ниже коде — нет. Я добавил данный класс в диаграмму, только лишь для лучшего понимания паттерна.

// Интерфайс абстракции
class AlarmClock {
private:
  virtual void toWake() = 0;
protected:
  /**
    It`s our bridge to implementation
  */
  AlarmClockImpl *bridge;
public:

  virtual void start() = 0;
  virtual void stop() = 0;
  
};

// Интерфейс реализации
class AlarmClockImpl {
public:
  virtual void ring() = 0;
  virtual void notify() = 0;
};

// Уточнение абстракции будильника в качестве зависающего
class LockupAlarmClock : public AlarmClock {
private:
  WORD hourAlarm;    // час расплаты
  WORD minutesAlarm;  // минута расплаты
  bool waitForWake;   // флаг признака ожидания

  virtual void toWake();
protected:
public:
  LockupAlarmClock(AlarmClockImpl& bridgeImpl, WORD hour, WORD minutes);

  virtual void start();
  virtual void stop();
};

LockupAlarmClock::LockupAlarmClock(AlarmClockImpl& bridgeImpl, WORD hour, WORD minutes) {
  this->bridge = &bridgeImpl;
  this->waitForWake = false;
  
  this->hourAlarm = hour;
  this->minutesAlarm = minutes;
}

// Вставай!!! Вставай!!
void LockupAlarmClock::toWake() {
  this->bridge->notify();
  this->bridge->ring();
}

// Запускаем процесс "зависания" будильника
void LockupAlarmClock::start() {
  // start lockup process
  SYSTEMTIME time;
  waitForWake = true;

  while (waitForWake) {
      
    GetLocalTime(&time);

    if (time.wHour == this->hourAlarm && time.wMinute == this->minutesAlarm) {
      waitForWake = false;
    }

    Sleep(100);
  }  

  toWake();
}

void LockupAlarmClock::stop() {
  // stop lockup process
  waitForWake = false;
}

// Уточнение интерфейса реализации в качестве будильника, играющего MP3 через внешнюю команду
class ShellMP3AlarmClock : public AlarmClockImpl {
private:
  string cmdplay; // сама команда
protected:
public:
  ShellMP3AlarmClock(const string& cmd);
  ~ShellMP3AlarmClock();

  virtual void ring();
  virtual void notify();
  
};

ShellMP3AlarmClock::ShellMP3AlarmClock(const string& cmd) {
  this->cmdplay = cmd;
}

void ShellMP3AlarmClock::ring() {
  // run command
  system(cmdplay.c_str());
}

void ShellMP3AlarmClock::notify() {
  cout << "ALARMING!" << endl;
}


* This source code was highlighted with Source Code Highlighter.


Продолжаем изучать паттерны :)

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

upd2: Да я мог воспользоваться кроном, если бы мне действительно был нужен будильник.

upd3: Да я мог воспользоваться планировщиком виндовс.

Tags:
Hubs:
+31
Comments72

Articles