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.
NOTES
In this article, we discuss the implementation of the Adapter Pattern in Go.
See the Adapter in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Adapter Pattern-
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.
New Implementation –
In the actual implementation, these elements come as new changes, after the pre-existing elements.
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-
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 the 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()
}
GoWe will get the below output if we run the above code –
Inside NewOperation1
Operation 1 of OldExampleStruct
Inside NewOperation2
Operation 2 of OldExampleStruct
PlaintextExamples
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 implementations 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
// file.go
package main
type File interface {
ReadFile() string
WriteFile(input string)
}
GoFileOp 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: %vn", input)
}
GoAPI Interface
// api.go
package main
type Api interface {
FetchData() string
SendData(data string)
}
GoNative API Struct [New Implementation]
// 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)
}
GoThird-Party API Struct [New 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: %vn", data)
}
GoNew 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
// 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)
}
GoDemo
To use the above implementation, use the 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")
}
GoOutput
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
PlaintextExample #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
// air_transport.go
package main
type AirTransport interface {
GetNumberOfEngines() int
GetNumberOfWheels() int
GetWeight() float64
// In Nautical miles
GetDistanceTravelled() float64
GetTravelCostTotal() float64
}
GoPlane Struct [implements AirTransport]
// 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
}
GoHelicopter Struct [implements AirTransport]
// 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
}
GoTransport Interface
// transport.go
package main
type Transport interface {
GetNumberOfWheels() int
GetWeight() float64
// In miles
GetDistanceTravelled() float64
GetTravelCostPerMile() float64
}
GoBus Struct [implements Transport]
// 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
}
GoBike Struct [implements Transport]
// 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
}
GoAirTransportAdapter Struct [implements Transport]
// 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()
}
GoDemo
// 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())
}
GoOutput
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
PlaintextSource Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check the implementation of adapter patterns in other programming languages.