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.