Вопрос о реализации Bridge Pattern

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

#include <iostream>

class Device
{
protected:
    int volume_m{ 0 };
public:
    int getVolume()
    {
        return volume_m;
    }
    void setVolume(int value)
    {
        volume_m = value;
    }
};

class RemoteController
{
private:
    std::shared_ptr<Device> device_m;
public:
    RemoteController(std::shared_ptr<Device> device) : device_m(device) {}
    void volumeUp()
    {
        device_m->setVolume(device_m->getVolume()+10);
        std::cout << "Volume turned up. Current volume: " << device_m->getVolume() << '\n';
    }
    void volumeDown()
    {
        this->device_m->setVolume(this->device_m->getVolume() - 10);
        std::cout << "Volume turned down. Current volume: " << device_m->getVolume() << '\n';
    }
    
};

class TV : public Device
{

};

class Radio : public Device
{

};


int main()
{
    std::shared_ptr<Device> tv = std::make_shared<TV>();
    std::shared_ptr<RemoteController> controller = std::make_shared<RemoteController>(tv);
    controller->volumeUp();
    controller->volumeUp();
    controller->volumeUp();
}

Что, если я хочу сделать разные сообщения для TV и Radio? Должен ли я создавать в Device виртуальные методы с именами volumeUp() и volumeDown(), которые будут унаследованы Radio и TV? И RemoteController будет вызывать только эти виртуальные методы?


person Thorvas    schedule 07.07.2020    source источник


Ответы (2)


Да, я считаю, что правильнее будет реализовать VolumeUp VolumeDown методы в Radio и TV объектах. Так как они потенциально могут различаться для этих объектов (не шаг 10 для обоих).

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

person alex_noname    schedule 07.07.2020

Должен ли я создавать в Device виртуальные методы с именами volumeUp() и volumeDown(), которые будут унаследованы Radio и TV? И RemoteController будет вызывать только эти виртуальные методы?

Да, короче говоря, шаблон моста использует делегирование. RemoteController делегирует функции-члены, определенные интерфейсом Device. TV и Radio могут переопределять виртуальные функции-члены Device.

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

Пример иерархии одного класса — без моста

У вас есть два разных типа устройств: телевизор и радио. Предположим, у вас есть два разных пульта дистанционного управления: радиочастотный и инфракрасный. Тогда у вас будет четыре класса: RadioIRController, RadioRFController, TVIRController, TVRFController. Эти классы будут реализовывать интерфейс RemoteController, определяющий виртуальные функции-члены volumeUp() и volumeDown(), т. е. единую иерархию (т. е. RemoteController).

В этом проекте, если вы теперь хотите добавить новый тип пульта дистанционного управления, например, ультразвуковой контроллер, вам придется создать два дополнительных класса: RadioUSController и TVUSController. Если вы, в свою очередь, захотите добавить новый тип устройства, например, домашнюю стереосистему, вам потребуется создать три дополнительных класса: HomeStereoIRController, HomeStereoRFController и HomeStereoUSController.

Как видите, расширять этот дизайн утомительно. Это связано с тем, что два ортогональных свойства — тип устройства и удаленный контроллер — смешаны в одной иерархии классов. Это сильная мотивация для перехода на паттерн «Мост».

одна иерархия

Применение шаблона моста

Вы разделяете единую иерархию на две: Controller и Device. Таким образом, каждая иерархия может быть расширена независимо друг от друга.

мост

Мы сделали это, частично отказавшись от наследования, используя композицию и полагаясь на делегирование: RemoteController ссылается на Device и вызывает операции над этим Device объектом.

Расширение этой схемы для дополнительной поддержки домашней стереосистемы потребует только создания нового класса HomeStereo, который наследуется от Device. Аналогичным образом, расширение этой схемы для поддержки ультразвукового контроллера потребует лишь создания нового класса USController, наследуемого от RemoteController. Теперь вы можете легко увидеть разницу.

person 眠りネロク    schedule 25.09.2020