Design Pattern: Bridge Pattern in Go

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.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.