Bridge pattern is used to provide flexibility to an implementation, that has frequent changes in the implementation. Bridge pattern archives that by decoupling implementation and abstraction.
NOTES
In this article, we discuss the implementation of the Bridge Pattern in Go.
See the Bridge in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Bridge Pattern-
Implementation
Following these steps to implement Bridge pattern in GoLang-
Let’s take an example where we have a few modules, and also there are few plugins. We want to use the plugins in the modules, but we don’t want to specify the plugin for a module.
Any plugin can be used for any module. Here is how to achieve that-
- Create an interface for the plugins.
- Create plugin structs, and implement the plugin interface for those. This way all the plugins have a common interface.
- Then create an interface for the modules.
- Create structs for modules, and implement the module interface.
- Add a property to store the plugin reference in the module structs.
- While creating a module object, pass the reference of a plugin, to the module.
- While performing operations in the module, use the plugin where required.
Here is the code for implementing the modules and plugin usage-
// Bridge pattern implementation
package main
import "fmt"
// Module interface
type Module interface {
MoudleOperation1()
}
// First module
type Module1 struct {
plugin Plugin
}
func NewModule1(plugin Plugin) (module1 *Module1) {
module1 = &Module1{}
module1.plugin = plugin
return
}
func (module1 *Module1) MoudleOperation1() {
fmt.Println("Module 1: operation 1")
module1.plugin.PluginOperation1()
}
// Second module
type Module2 struct {
plugin Plugin
}
func NewModule2(plugin Plugin) (module2 *Module2) {
module2 = &Module2{}
module2.plugin = plugin
return
}
func (module2 *Module2) MoudleOperation1() {
fmt.Println("Module 2: operation 1")
module2.plugin.PluginOperation1()
}
// Plugin interface
type Plugin interface {
PluginOperation1()
}
// First plugin
type Plugin1 struct {}
func NewPlugin1() (plugin1 *Plugin1) {
plugin1 = &Plugin1{}
return
}
func (plugin1 *Plugin1) PluginOperation1() {
fmt.Println("Plugin 1: operation 1")
}
// Second plugin
type Plugin2 struct {}
func NewPlugin2() (plugin2 *Plugin2) {
plugin2 = &Plugin2{}
return
}
func (plugin2 *Plugin2) PluginOperation1() {
fmt.Println("Plugin 2: operation 1")
}
// Demo
func main() {
module1 := NewModule1(NewPlugin2())
module1.MoudleOperation1()
}
Output of the above code will be as below.
Module 1: operation 1
Plugin 2: operation 1
Examples
Here are a few examples of Bridge pattern implementation in GoLang.
Example #1: UI Elements
In the example below we are implementing the Bridge pattern for drawing UI Elements.
There are few color schemas and there are UI elements. We want to use the color schema for the UI elements.
Color Schema Interface
- Create file “color_schema.go”.
- Create interface “ColorSchema”.
- Declare methods for the interface. Here we have method – “SetColor”.
// color_schema.go
package main
type ColorSchema interface {
SetColor()
}
Red [Color Schema]
- Create file “red.go”.
- Create struct “Red”.
- Implement interface “ColorSchema” for “Red” struct. Define method “SetColor” as part of the interface implementation.
// red.go
package main
import "fmt"
type Red struct {
}
func NewRed() (red *Red) {
red = &Red{}
return
}
func (red *Red) SetColor() {
fmt.Println("Setting proper color for Red color schema")
}
Green [Color Schema]
- Create file “green.go”.
- Create struct “Green”.
- Implement interface “ColorSchema” for “Green” struct.
// green.go
package main
import "fmt"
type Green struct {
}
func NewGreen() (green *Green) {
green = &Green{}
return
}
func (green *Green) SetColor() {
fmt.Println("Setting proper color for Green color schema")
}
Blue [Color Schema]
- Create file “blue.go”.
- Create struct “Blue”.
- Implement interface “ColorSchema” for “Blue” struct.
// blue.go
package main
import "fmt"
type Blue struct {
}
func NewBlue() (blue *Blue) {
blue = &Blue{}
return
}
func (blue *Blue) SetColor() {
fmt.Println("Setting proper color for Blue color schema")
}
UI Element Interface
- Create file “ui_element.go”.
- Create interface “UIElement”.
- Declare method “PrintElement” for the interface.
// ui_element.go
package main
type UIElement interface {
PrintElement()
}
Button [UI Element]
- Create file “button.go”.
- Create struct “Button”.
- Define property “colorSchema” of type “ColorSchema”. This is used to store the color schema that will be used for the button.
- Define method “NewButton”, which accepts param of type “ColorSchema”. Create an object of type “Button”, and assign the value of “colorSchema” for the button. then return the button object.
- Implement interface “UIElement” for “Button” struct. Define the method “PrintElement” for the struct. In the method use the “SetColor” method from “colorSchema”.
// button.go
package main
import "fmt"
type Button struct {
colorSchema ColorSchema
}
func NewButton(colorSchema ColorSchema) (button *Button) {
button = &Button{}
button.colorSchema = colorSchema
return
}
func (button *Button) PrintElement() {
button.colorSchema.SetColor()
fmt.Println("Printing Button")
}
Input [UI Element]
- Create file “input.go”.
- Create struct “Input”.
- Define field “colorSchema” of type “ColorSchema”.
- Define method “NewInput”, with param of type “ColorSchema”. Create “Input” object, assign the value of “colorSchema” for the input, and then return the input object.
- Implement interface “UIElement” for “Input” struct.
// input.go
package main
import "fmt"
type Input struct {
colorSchema ColorSchema
}
func NewInput(colorSchema ColorSchema) (input *Input) {
input = &Input{}
input.colorSchema = colorSchema
return
}
func (input *Input) PrintElement() {
input.colorSchema.SetColor()
fmt.Println("Printing Input")
}
Table [UI Element]
- Create file “table.go”.
- Create struct “Table”.
- Define field “colorSchema” of type “ColorSchema”.
- Define method “NewTable”, with param type “ColorSchema”. Create “Table” object, assign the value of “colorSchema” for the input, and then return the table object.
- Implement interface “UIElement” for “Table” struct.
// table.go
package main
import "fmt"
type Table struct {
colorSchema ColorSchema
}
func NewTable(colorSchema ColorSchema) (table *Table) {
table = &Table{}
table.colorSchema = colorSchema
return
}
func (table *Table) PrintElement() {
table.colorSchema.SetColor()
fmt.Println("Printing Table")
}
Demo
To use the implementation, pass an object of type “ColorSchema” (Red, Green, or Blue), while creating a new “UIElement” object. Check example below-
// main.go
package main
func main() {
table := NewTable(NewRed())
table.PrintElement()
input := NewInput(NewGreen())
input.PrintElement()
button := NewButton(NewBlue())
button.PrintElement()
button2 := NewButton(NewRed())
button2.PrintElement()
}
Output
The output of the demo above will be like below.
Setting proper color for Red color schema
Printing Table
Setting proper color for Green color schema
Printing Input
Setting proper color for Blue color schema
Printing Button
Setting proper color for Red color schema
Printing Button
Example #2: Transport Seat
For this example, we have 2 modes of transport- Train and Plane. And there are 2 types of seats available for transport – Economy and Business class. Users can select any type of seat for any type of transport.
The implementation for transport and seats are completely separate. Using Bridge pattern we can implement it in such a way that the seats can be selected and used for any transport while using the transports.
Seat Interface
- Create file “seat.go”.
- Create interface “Seat”.
- Declare method “SelectSeat” for the interface.
// seat.go
package main
type Seat interface {
SelectSeat()
}
Business Class Seat [Seat Type]
- Create file “business_class_seat.go”.
- Create struct “BusinessClassSeat”.
- Define method “NewBusinessClassSeat”, which is responsible for generating new “BusinessClassSeat” object.
// busines_class_seat.go
package main
import "fmt"
type BusinessClassSeat struct {
}
func NewBusinessClassSeat() (businessClassSeat *BusinessClassSeat) {
businessClassSeat = &BusinessClassSeat{}
return
}
func (businessClassSeat *BusinessClassSeat) SelectSeat() {
fmt.Println("Select a Business class seat")
}
Economy Class Seat [Seat Type]
- Create file “economy_class_seat.go”.
- Create struct “EconomyClassSeat”.
- Define method “NewEconomyClassSeat” for creating new “BusinessClassSeat” object.
// economy_class_seat.go
package main
import "fmt"
type EconomyClassSeat struct {
}
func NewEconomyClassSeat() (economyClassSeat *EconomyClassSeat) {
economyClassSeat = &EconomyClassSeat{}
return
}
func (economyClassSeat *EconomyClassSeat) SelectSeat() {
fmt.Println("Select an Economy class seat")
}
Transport Interface
- Create file “transport.go”.
- Create “Transport” interface.
- Declare method “SelectTransport”.
// transport.go
package main
type Transport interface {
SelectTransport()
}
Plane [Transport]
- Create file “plane.go”.
- Create “Plane” struct.
- Declare field “seat” of type “Seat”.
- Create method “NewPlane” that takes “Seat” as a param. This method creates a new “Plane” object, sets “seat” field value and then returns the “Plane” object.
- Implement interface “Transport” for “Plane” struct. Define method “SelectTransport” as part of interface implementation. Then “SelectTrasnport” method performs required operation, also it uses the “SelectSeat” method from “seat”.
// plane.go
package main
import "fmt"
type Plane struct {
seat Seat
}
func NewPlane(seat Seat) (plane *Plane) {
plane = &Plane{}
plane.seat = seat
return
}
func (plane *Plane) SelectTransport() {
fmt.Println("Plane selected for transport")
plane.seat.SelectSeat()
}
Train [Transport]
- Create file “train.go”.
- Create “Train” struct.
- Declare field “seat” of type “Seat”.
- Create method “NewTrain” that takes “Seat” as a param, and creates new “Train” object.
- Define method “SelectTransport”, which uses the “SelectSeat” method of “seat”.
// train.go
package main
import "fmt"
type Train struct {
seat Seat
}
func NewTrain(seat Seat) (train *Train) {
train = &Train{}
train.seat = seat
return
}
func (train *Train) SelectTransport() {
fmt.Println("Train selected for transport")
train.seat.SelectSeat()
}
Demo
To use the implementation, pass a “Seat” object while creating a “Plane”/”Train” object. Then we can use the “SelectTransport” method of the Transport object.
// main.go
package main
func main() {
plane := NewPlane(NewBusinessClassSeat())
plane.SelectTransport()
plane2 := NewPlane(NewEconomyClassSeat())
plane2.SelectTransport()
train := NewTrain(NewEconomyClassSeat())
train.SelectTransport()
}
Output
We will get the following output.
Plane selected for transport
Select a Business class seat
Plane selected for transport
Select an Economy class seat
Train selected for transport
Select an Economy class seat
Source Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check Bridge pattern implementation in other programming languages.