Design Pattern: Adapter Pattern in Go

Adapter pattern helps by enabling an interface to adapt to a new interface. This is useful when we have an existing interface, and some new requirement comes or we need to adapt to some interface that is developed separately.

By following the Open/Closed principle, the adapter pattern keeps the existing interface as is, and adds the adapter implementation as a bridge to the new interface.

This article demonstrates Adapter pattern implementations in Go. Check the implementation details and examples below.

Implementation

Here are the steps to follow for Adapter pattern implementation-

Pre-existing –

In the actual implementation these elements are supposed to be pre-existing. But we will create these here to demonstrate the adapter implementation.

  • Define an interface and declare some methods.
  • Create structs and implement the interface.

New Implementation –

In the actual implementation, these elements come as new changes, after the pre-existing elements.

  • Define a new interface and declare some methods. 
  • Create structs and implement the new interface.

Adapter Implementation –

Now as per the requirements, we have to enable the old interface, to adapt to the new interface. The following steps represent the actual Adapter implementation-

  • Define a struct for the Adapter.
  • Declare a property in the struct to hold an instance of the old interface.
  • While creating the adapter object, set an old interface value.
  • Implement the new interface for the Adapter struct.
  • In the method implementations, use the methods from the old interface implementation.

While using the adapter, we have to create an instance of the old instance struct object, then we have to pass that to the adapter.

Here is a sample implementation of Adapter pattern in Go –

package main

import "fmt"

type OldInterface interface {
	Operation1()
	Operation2()
}

// Old interface implementation
type OldExampleStruct struct {
}

func getOldExampleStruct() (oldExampleStruct *OldExampleStruct) {
	oldExampleStruct = &OldExampleStruct{}
	return
}

func (OldExampleStruct *OldExampleStruct) Operation1() {
	fmt.Println("Operation 1 of OldExampleStruct")
}

func (OldExampleStruct *OldExampleStruct) Operation2() {
	fmt.Println("Operation 2 of OldExampleStruct")
}

// New interface
type NewInterface interface {
	NewOperation1()
	NewOperation2()
	NewOperation3()
}

// New interface implementation
type NewExampleStruct struct {
}

func getNewExampleStruct() (newExampleStruct *NewExampleStruct) {
	newExampleStruct = &NewExampleStruct{}
	return
}

func (newExampleStruct *NewExampleStruct) NewOperation1() {
	fmt.Println("NewOperation 1 of NewExampleStruct")
}

func (newExampleStruct *NewExampleStruct) NewOperation2() {
	fmt.Println("NewOperation 2 of NewExampleStruct")
}

func (newExampleStruct *NewExampleStruct) NewOperation3() {
	fmt.Println("NewOperation 3 of NewExampleStruct")
}

// New adapter for the OldInterface
type Adapter struct {
	oldInterface OldInterface
}

func getAdapter(oldInterface OldInterface) (adapter *Adapter) {
	adapter = &Adapter{}
	adapter.oldInterface = oldInterface
	return
}

func (adapter *Adapter) NewOperation1() {
	// May perform additional operation or processing
	// before or after
	fmt.Println("Inside NewOperation1")

	adapter.oldInterface.Operation1()
}

func (adapter *Adapter) NewOperation2() {
	// May perform additional operation or processing
	// before or after
	fmt.Println("Inside NewOperation2")

	adapter.oldInterface.Operation2()
}

func (adapter *Adapter) NewOperation3() {
	panic("Operation not avaiable")
}


// Demo
func main() {
	// Make a call to the old example via adapter
	oldStruct := getOldExampleStruct()
	adapter := getAdapter(oldStruct)
	
	adapter.NewOperation1()
	adapter.NewOperation2()
}

We will get the below output if we run the above code –

Inside NewOperation1
Operation 1 of OldExampleStruct

Inside NewOperation2
Operation 2 of OldExampleStruct

Examples

Here are a few examples of Adapter pattern implementation in GoLang.

Example #1: API with File Adapter

Here we have an interface for “File” and implementation of the interface. These implementation deal with local file operations, like reading, and writing files.

As a new implementation, we have an API calling process. This has implementations for native and third-party API calls.

Now as a new requirement, we want to use the API interface to be implemented for the file operations. So that we can make the file operations calls as API calls, and treat file read/write as an API.

File Interface

  • Create file “file.go”.
  • Create interface “File”.
  • Declare methods “ReadFile” and “WriteFile”.
// file.go

package main

type File interface {
	ReadFile() string
	WriteFile(input string)
}

FileOp Struct

  • Create file “file_op.go”.
  • Create struct “FileOp”.
  • Define the method “NewFileOp” which is responsible for creating a new object of “FileOp”.
  • Implement interface “File”. As a part of the implementation define methods “ReadFile” and “WriteFile” for the “FileOp” struct.
// file_op.go

package main

import "fmt"

type FileOp struct {
}

func NewFileOp() (fileOp *FileOp) {
	fileOp = &FileOp{}
	return
}

func (fileOp *FileOp) ReadFile() (string) {
	// Code to read from file

	fmt.Println("Reading from file")
	return "some dummy response read from file"
}

func (fileOp *FileOp) WriteFile(input string) {
	// Write to file related code here

	fmt.Printf("Writing to file: %v\n", input)
}

API Interface

  • Create file “api.go”.
  • Create interface “Api”.
  • Declare methods “FetchData” and “SendData”.
// api.go

package main

type Api interface {
	FetchData() string
	SendData(data string)
}

Native API Struct [New Implementation]

  • Create file “native_api.go”.
  • Create struct “NativeApi”.
  • Implement interface “Api” for the struct. Define methods “FetchData” and “SendData” for the struct.
// native_api.go

package main

import "fmt"

type NativeApi struct {
}

func NewNativeApi() (nativeApi *NativeApi) {
	nativeApi = &NativeApi{}
	return
}

func (nativeApi *NativeApi) FetchData() (string) {
	// code to fetch data from native API

	fmt.Println("Fetching data from Native API")
	return "Data read from Native Api"
}

func (nativeApi *NativeApi) SendData(data string) {
	// code to send data to native API

	fmt.Printf("Sending data to Native API: %v", data)
}

Third-Party API Struct [New Implementation]

  • Create file “third_part_api.go”.
  • Create struct “ThirdPartyApi”.
  • Implement interface “Api” for “ThirdPartyApi”. Define methods “FetchData” and “SendData” for the interface implementation.
// third_party_api.go

package main

import "fmt"

type ThirdPartyApi struct {
}

func NewThirdPartyApi() (thirdPartyApi *ThirdPartyApi) {
	thirdPartyApi = &ThirdPartyApi{}
	return
}

func (thirdPartyApi *ThirdPartyApi) FetchData() (string) {
	// code to fetch data from Third Party API

	fmt.Println("Fetching data from Third Party API")
	return "Data read from Third Party Api"
}

func (thirdPartyApi *ThirdPartyApi) SendData(data string) {
	// code to send data to Third Party API

	fmt.Printf("Sending data to Third Party API: %v\n", data)
}

New Requirement

As per the new requirement, we have to enable the “File” interface to adapt to the “Api” interface. So that we can make the file operations like an API call.

File Adapter

  • Create file “file_adapter.go”.
  • Create struct “FileAdapter”.
  • Declare a property “file” of type “File” interface. Set it while creating the adapter object.
  • Implement the “Api” interface for the struct. 
  • In the implemented methods use methods from the “file” object.
// file_adapter.go

package main

type FileAdapter struct {
	file File
}

func NewFileAdapter(file File) (fileAdapter *FileAdapter) {
	fileAdapter = &FileAdapter{}
	fileAdapter.file = file
	return
}

func (fileAdapter *FileAdapter) FetchData() string {
	// May perform additional operation or processing
	// before or after data is fetched

	return fileAdapter.file.ReadFile()
}

func (fileAdapter *FileAdapter) SendData(data string) {
	// May perform additional operation or processing
	// before or after data is written to file

	fileAdapter.file.WriteFile(data)
}

Demo

To use the above implementation, us following code.

// main.go

package main

func main() {
	// make a call to third part API for testing
	thirdPartyApi := NewThirdPartyApi()
	thirdPartyApi.FetchData()
	thirdPartyApi.SendData("1234")

	// Make a call to the file via FileAdapter
	file := NewFileOp()
	fileAdapter := NewFileAdapter(file)
	fileAdapter.FetchData()
	fileAdapter.SendData("ABCDEF")
}

Output

We will get the following output-

Fetching data from Third Party API
Sending data to Third Party API: 1234

Reading from file
Writing to file: ABCDEF

Example #2: Transport

In the existing code, we already have Plane and Helicopter structs. Both implement an interface named AirTransport.

Later we created an interface named Transport. Some new transport structs are created. And all the transport-related structs are supposed to implement the Transport interface.

The problem is, AirTransport and Transport interface does not match each other. So, it will be difficult to implement the Transport interface for Plane and Helicopter.

We will use the adapter pattern to solve this. Let’s check, step-by-step.

AirTransport Interface

  • Create file “air_transport.go”.
  • Create interface ‘AirTransport”.
  • Declare methods – “GetNumberOfEngines”, “GetNumberOfWheels”, “GetWeight”, “GDistanceTravelled”, “GetTravelCostTotal”.
// air_transport.go

package main

type AirTransport interface {
	GetNumberOfEngines() int

	GetNumberOfWheels() int

	GetWeight() float64

	// In Nautical miles
	GetDistanceTravelled() float64
	
	GetTravelCostTotal() float64
}

Plane Struct[implements AirTransport]

  • Create file “plane.go”.
  • Create struct ‘Plane”.
  • Create method “NewPlane” which is responsible for creating a new “Plane” object.
  • Implement interface “AirTransport” interface. For the implementation define methods – “GetNumberOfEngines”, “GetNumberOfWheels”, “GetWeight”, “GDistanceTravelled”, “GetTravelCostTotal”.
// plane.go

package main

type Plane struct {
}

func NewPlane() (plane *Plane) {
	plane = &Plane{}
	return
}

func (plane *Plane) GetNumberOfEngines() int {
	return 2
}

func (plane *Plane) GetNumberOfWheels() int {
	return 3
}

func (plane *Plane) GetWeight() float64 {
	return 127_000
}

func (plane *Plane) GetDistanceTravelled() float64 {
	return 500 // Nautical miles
}

func (plane *Plane) GetTravelCostTotal() float64 {
	return 3_000
}

Helicopter Struct [implements AirTransport]

  • Create file “helicopter.go”.
  • Create struct ‘Helicopter”.
  • Create method “NewHelicopter” which is responsible for creating a new “Helicopter” object.
  • Implement interface “AirTransport” interface for “Helicopter” struct. For the implementation define methods – “GetNumberOfEngines”, “GetNumberOfWheels”, “GetWeight”, “GDistanceTravelled”, “GetTravelCostTotal”.
// helicopter.go

package main

type Helicopter struct {
}

func NewHelicopter() (helicopter *Helicopter) {
	helicopter = &Helicopter{}
	return
}


func (helicopter *Helicopter) GetNumberOfEngines() int {
	return 1
}

func (helicopter *Helicopter) GetNumberOfWheels() int {
	return 0
}

func (helicopter *Helicopter) GetWeight() float64 {
	return 12_000
}

func (helicopter *Helicopter) GetDistanceTravelled() float64 {
	return 180
}


func (helicopter *Helicopter) GetTravelCostTotal() float64 {
	return 20_000
}

Transport Interface

  • Create file “transport.go”.
  • Create interface ‘Transport”.
  • Declare methods – “GetNumberOfWheels”, “GetWeight”, “GetDistanceTravelled”, “GetTravelCostPerMile”.
// transport.go

package main

type Transport interface {
	GetNumberOfWheels() int

	GetWeight() float64

	// In miles
	GetDistanceTravelled() float64
	
	GetTravelCostPerMile() float64
}

Bus Struct [implements Transport]

  • Create file “bus.go”.
  • Create struct ‘Bus”.
  • Create method “NewBus”. This method creates a new “Bus” object and returns that.
  • Implement “Transport” interface for “Bus” struct.
// bus.go

package main

type Bus struct {
}

func NewBus() (bus *Bus) {
	bus = &Bus{}
	return
}

func (bus *Bus) GetNumberOfWheels() int {
	return 4
}

func (bus *Bus) GetWeight() float64 {
	return 10_000
}

func (bus *Bus) GetDistanceTravelled() float64 {
	return 1_000
}

func (bus *Bus) GetTravelCostPerMile() float64 {
	return 5
}

Bike Struct [implements Transport]

  • Create file “bike.go”.
  • Create struct “Bike”.
  • Create method “NewBike”. This method creates a new “Bike” object and returns that.
  • Implement “Transport” interface for “Bike”.
// bike.go

package main

type Bike struct {
}

func NewBike() (bike *Bike) {
	bike = &Bike{}
	return
}

func (bike *Bike) GetNumberOfWheels() int {
	return 2
}

func (bike *Bike) GetWeight() float64 {
	return 700
}

func (bike *Bike) GetDistanceTravelled() float64 {
	return 80
}

func (bike *Bike) GetTravelCostPerMile() float64 {
	return 4
}

AirTransportAdapter Struct [implements Transport]

  • Create file “air_transport_adapter.go”.
  • Create struct “AirTransportAdapter”.
  • In the struct define a property named “airTransport” which is of type “AirTransport”.
  • Define method “NewAirTransportAdapter” for creating a new adapter object. This method takes an object of type “AirTransport” and set that to the “airTransport” property.
  • Implement “Transport” interface for the adapter. Define methods “GetNumberOfWheels”, “GetWeight”, “GetDistanceTravelled”, “GetTravelCostPerMile”, as part of the interface implementation.
  • In the method implementation use method from the “AirTransport” interface, which is stored in the property “airTransport”.
// air_transport_adapter.go

package main

type AirTransportAdapter struct {
	airTransport AirTransport
}

func NewAirTransportAdapter(airTransport AirTransport) (airTransportAdapter *AirTransportAdapter) {
	airTransportAdapter = &AirTransportAdapter{}
	airTransportAdapter.airTransport = airTransport
	return
}

func (airTransportAdapter *AirTransportAdapter) GetNumberOfWheels() int {
	return airTransportAdapter.airTransport.GetNumberOfWheels()
}

func (airTransportAdapter *AirTransportAdapter) GetWeight() float64 {
	return airTransportAdapter.airTransport.GetWeight()
}

func (airTransportAdapter *AirTransportAdapter) GetDistanceTravelled() float64 {
	// Convert nautical mile to mile and return
	distanceInNauticalMile := airTransportAdapter.airTransport.GetDistanceTravelled()
	return distanceInNauticalMile * 1.151
}

func (airTransportAdapter *AirTransportAdapter) GetTravelCostPerMile() float64 {
	// calculate cost per mile from total cost
	totalCost := airTransportAdapter.airTransport.GetTravelCostTotal()
	return totalCost / airTransportAdapter.GetDistanceTravelled()
}

Demo

  • Create file “main.go”.
  • Define the main method.
  • For using the adapter, pass an object of type “AirTransport”, while creating an adapter object. Here we have passed “NewPlane()” to the adapter.
// main.go

package main

import "fmt"

func main() {
	fmt.Println("Get information of Bus travel...")
	
	bus := NewBus()
	fmt.Printf("\nNumber of wheels: %v", bus.GetNumberOfWheels())
	fmt.Printf("\nWeight(kg): %v", bus.GetWeight())
	fmt.Printf("\nDistance(miles): %v", bus.GetDistanceTravelled())
	fmt.Printf("\nCost per mile: %v", bus.GetTravelCostPerMile())

	fmt.Println("\nGet information of Plane travel...")
	
	planeTransport := NewAirTransportAdapter(NewPlane())
	fmt.Printf("\nNumber of wheels: %v", planeTransport.GetNumberOfWheels())
	fmt.Printf("\nWeight(kg): %v", planeTransport.GetWeight())
	fmt.Printf("\nDistance(miles): %v", planeTransport.GetDistanceTravelled())
	fmt.Printf("\nCost per mile: %v", planeTransport.GetTravelCostPerMile())
}

Output

The following output will be generated-

Get information of Bus travel...

Number of wheels: 4
Weight(kg): 10000
Distance(miles): 1000
Cost per mile: 5


Get information of Plane travel...

Number of wheels: 3
Weight(kg): 127000
Distance(miles): 575.5
Cost per mile: 5.212858384013901

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Adapter pattern implementation in other programming languages.

Leave a Comment


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