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 😊