Chapter 20 : Series On Design Patterns – Bridge Pattern

Key Words: Bridge design pattern

Topics at a glance:

  • Let us bridge the abstract and the implementation entities
  • Let us turn on an incandescent bulb and adjust its brightness
  • Let us make a florescent bulb as well.

Bridge design pattern

Bridge is a structural design pattern, that separates the implementation and the abstraction into an orthogonally related two-class systems. One class system defines/represents the abstract entities, while another class system defines/represents the actual implementation entities.  

For explaining bridge pattern, I am taking the example of the Bulb and its State that we used in the Bulb Controller application of chapter 19. Could you identify the abstract class system in that application? The abstract class system that I am mentioning about is the State class and its derived classes On_State and Off_State. Why are they abstract? State itself is an abstract concept as I have explained in chapter 19. State object only knows to change from one sate to another and manipulate the behavior in each state. They do not really know how to implement the actual functionality that the machine has to perform in that state. For example, how a State class’s object instance will know how to turn on an electric bulb or how to turn off? It does not really.

We will come back to this example later in tis chapter. For citing another example for abstract class systems, consider Process and Thread in any platform.The actual implementation of “processes” and”threads” is platform dependent. Say, one can use C++ threads or posix threads in a program. But if the application/client code to an abstract base class with interfaces to create, run, join, and interact with the thread, what matters whether it’s a posix thread or C++ standard library thread implementation? Coming to process now. A process is defined/implemented by the underlying platform such as Widows, Linux, IOS operating systems or some virtual machine hypervisor. The client only programs to an abstract base class Process. It needs interfaces to create, fork, manage and to kill a process. That’s all the client bothers about.

In normal course, if we are writing a code to only one specific implementation, then no need to have separate abstract class system and implementation class system in your design. But if you want to write a (semi-) portable application that should run regardless of the underlying platform/implementation, then it is imperative that you must program to abstract class systems. Now, the focus comes to this; How you are going to define this abstract class system? Usually through inheritance. We define an abstract base class, and derive concrete classes that are specific to the platform – say Windows, Linux, Posix or C++ standard library. But what if we introduce another platform in future, say, a virtual hypervisor or a simple bare-metal system with a minimal layer supporting process/threads. We cannot continue to inherit which will lead to inheritance misuse as we saw in case of the Page class that I used as an example in chapter 16 – for understanding the need for decorator design pattern. But here, we cannot resort to decorator pattern, as we need a way to abstract the implementation and our purpose is not to add features/functionalities on top of a base class. We will need a way to separate the abstract class system from the implementation class system. This is where bridge pattern comes in for help.

Bridge pattern separates the abstract class system that define abstract entities from actual implementation class system that is platform dependent. Let us come back to our light control application. Here, user need a feature to add support for incandescent bulbs that supports dimming/adjusting the brightness and for fluorescent bulb technology that gives more light for lesser power.

Turning on, off, changing the brightness cannot be defined in the abstract Bulb class or in the abstract State class. We need another implementation specific class system called the Light_Device class and its concrete derived classes that implements the functionality to support incandescent and fluorescent technologies.

Now, Bulb class will also contain a pointer reference to the Light_Device object instance in addition to the State instance. But, Bulb, as before, delegates the state manipulation to its State object and State object after managing the state, delegates the task for turning on or turning off the light bulb to the Light_Device object instance that the Bulb object possess. Bulb class also have to define one interface to get the Light_Device object instance for delegating these tasks to turn on, off and change brightness to Light_Device object through State object.

Now, let us see the bridge pattern in action.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include <iostream>
#include <string>


using namespace std;

enum class light_technology{incandescent = 0, fluorescent};

class Light_Device
{
public:
    ~Light_Device(){}
    virtual void on() =0;
    virtual void off() =0;
    virtual void change_brightness() =0;
};

class Incandescent_Device : public Light_Device
{
private:
    float current_brightness;
public:
    Incandescent_Device() : current_brightness{0.0F}
    {}
    ~Incandescent_Device(){}
    void on()
    {
        current_brightness = 100.0F;
        cout << "Incandescent bulb turned on" << endl;
    }
    
    void off()
    {
        current_brightness = 0.0F;
        cout << "Incandescent bulb turned off" << endl;
    }
    
    void change_brightness()
    {
        cout << "Enter the required brightness level" << endl;
        float percent;
        cin >> percent;
        if((percent < 0.0F) || (percent > 100.0F))
        {
            cout << "Invalid brightness entered" << endl;
        }
        else 
        {
            current_brightness = percent;
            cout << "Brightness set at " << current_brightness << endl; 
        }
        
    }
    
};

class Fluorescent_Device : public Light_Device
{
public:
    Fluorescent_Device(){}
    ~Fluorescent_Device(){}
    void on()
    {
        cout << "Fluorescent bulb turned on" << endl;
    }
    
    void off()
    {
        cout << "Fluorescent bulb turned off" << endl;
    }
    
    void change_brightness()
    {
        cout << "This operation not supported for fluorescent device" << endl;        
    }
    
};

class Bulb;

class State
{
public:
    State(){}
    virtual ~State(){}
    virtual void on(Bulb *bulb);
    virtual void off(Bulb *bulb);
    virtual void change_brightness(Bulb *bulb);
};

class Bulb
{
private:
    State *current;
    Light_Device *light_device;
public:
    Bulb(light_technology technology, State *state) : current{state}
    {
        switch(technology)
        {
            case light_technology::fluorescent:
            {
                light_device = new Fluorescent_Device();
                break;
            }
            case light_technology::incandescent:
            default:
            {
                light_device = new Incandescent_Device();
                break;
            }            
        }
    }
    ~Bulb()
    {
        delete light_device;
    }
    
    void set_current_state(State *state)
    {
        current = state;
        return;
    }
    
    void on()
    {
        current->on(this);
        return;
    }
    
    void off()
    {
        current->off(this);
        return;
    }
    
    void change_brightness()
    {    
        current->change_brightness(this);
        return;
    }
    
    Light_Device* get_light_device()
    {
        return light_device;
    }
    
};


void State::on(Bulb *bulb)
{
    cout << "already in on state" << endl;
    return;
}

void State::off(Bulb *bulb)
{
    cout << "already in off state" << endl;
    return;
}

void State::change_brightness(Bulb *bulb)
{
    cout << "Not supported in off state" << endl;
    return;
}


class On_State : public State
{
public:
    On_State(){}
    ~On_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
    void change_brightness(Bulb *bulb);
};

class Off_State : public State
{
public:
    Off_State(){}
    ~Off_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
    void change_brightness(Bulb *bulb);
};

void On_State::on(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::on(bulb);
}

void On_State::change_brightness(Bulb *bulb)
{
    auto light_device = bulb->get_light_device();
    light_device->change_brightness();
}

void On_State::off(Bulb *bulb)
{
    // create an Off_State instance
    Off_State *new_state = new Off_State();
    bulb->set_current_state(new_state);
    auto light_device = bulb->get_light_device();
    light_device->off();
    
    delete this;
}

void Off_State::on(Bulb *bulb)
{    
    // create an Off_State instance
    On_State *new_state = new On_State();
    bulb->set_current_state(new_state);
    auto light_device = bulb->get_light_device();
    light_device->on();
    
    delete this;
}

void Off_State::off(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::off(bulb);
    
    return;
}

void Off_State::change_brightness(Bulb *bulb)
{
    // nothing to do 
    // just delegate to base class 
    State::change_brightness(bulb);
}


int main()
{
    string choice;
    string technology;
    Bulb *b1;
    
    cout << "Bulb controller application" << endl;
    cout << "Enter the bulb technology required :\n(I) : Incandescent\n(F) : Fluorescent" << endl;
    
    cin >> technology;
    
    if(0 == technology.compare("F"))
    {
        b1 = new Bulb(light_technology::fluorescent, new Off_State());
    }
    else
    {
        b1 = new Bulb(light_technology::incandescent, new Off_State());
    }
    
    
    while(true)
    {
        choice.clear();
        cout << "What to do next : ON, OFF, SET_BRIGHTNESS, EXIT" << endl;
        cin >> choice;
        
        if(0 == choice.compare("ON"))
        {
            b1->on();
        }
        else if(0 == choice.compare("OFF"))
        {
            b1->off();
        }
        else if(0 == choice.compare("SET_BRIGHTNESS"))
        {
            b1->change_brightness();
        }
        else if(0 == choice.compare("EXIT"))
        {
            cout << "Exiting bulb controller application" << endl;
            break;
        }
    }
    
    if(b1 != nullptr)
    {
        delete b1;
    }
    
    return 0;
}

Want to see the result?

Bulb controller application
Enter the bulb technology required :
(I) : Incandescent
(F) : Fluorescent
I
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
ON
Incandescent bulb turned on
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
SET_BRIGHTNESS
Enter the required brightness level
50
Brightness set at 50
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
OFF
Incandescent bulb turned off
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
EXIT
Exiting bulb controller application

Incandescent bulb support changing the brightness. Now let us see what happens when the user chooses to create a fluorescent bulb instead.

Bulb controller application
Enter the bulb technology required :
(I) : Incandescent
(F) : Fluorescent
F
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
ON
Fluorescent bulb turned on
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
SET_BRIGHTNESS
This operation not supported for fluorescent device
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
OFF
Fluorescent bulb turned off
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
EXIT
Exiting bulb controller application

See the difference now? Both the Bulb and State classes does not really bother about the implementation. They just delegate the task that depends upon the implementation to the relevant implementation object. i.e. Light_Device instance. That’s the advantage of bridge pattern.

Enjoyed the chapter? Let me know in the comments below. Thanks 😊

0

Leave a Reply