Table of contents
The Adapter Pattern, one of the Gang of Four design patterns, is used to enable the interaction between two incompatible interfaces by creating a wrapper object that can translate requests between both interfaces. In Golang, this pattern is quite useful when you have two interfaces, but their functionalities do not match, and you need to make them work together.
Implementation
The Adapter Pattern consists of four components:
Target Interface: the interface that the client is expecting to interact with.
Adapter: the object that implements the Target Interface and wraps the Adaptee.
Adaptee: the interface that needs to be adapted to be used by the client.
Client: the component that uses the Target Interface.
Here is an example implementation of the Adapter Pattern in Golang:
type Target interface {
Request() string
}
type Adaptee interface {
SpecificRequest() string
}
type adapteeImpl struct {}
func (a *adapteeImpl) SpecificRequest() string {
return "Adaptee request"
}
type adapter struct {
adaptee Adaptee
}
func (a *adapter) Request() string {
return "Adapter: " + a.adaptee.SpecificRequest()
}
func main() {
adaptee := &adapteeImpl{}
target := &adapter{
adaptee: adaptee,
}
fmt.Println(target.Request())
}
In this example, we have two interfaces, Target
and Adaptee
, that are incompatible. We create a concrete implementation of Adaptee
, adapteeImpl
, and an adapter that implements the Target
interface and wraps the Adaptee
interface.
Real World Example
Let's take an example of a payment gateway that accepts payments from different payment providers like PayPal, Stripe, and Amazon Pay. Each payment provider has its own unique interface to connect with the payment gateway. If we want to add a new payment provider, we need to create a new interface and implement it.
To solve this problem, we can create an adapter for each payment provider that implements the payment gateway's standard interface. Let's take an example of PayPal payment provider:
// paypal.go
type PayPal struct {}
func (p *PayPal) MakePayment(amount float32) bool {
// connect to PayPal and process payment
return true
}
// amazon.go
type Amazon struct {}
func (a *Amazon) PayAmazon(amount float32) bool {
// connect to Amazon and process payment
return true
}
// gateway.go
type PaymentGateway interface {
ProcessPayment(amount float32) bool
}
type PayPalAdapter struct {
PayPal *PayPal
}
func (p *PayPalAdapter) ProcessPayment(amount float32) bool {
return p.PayPal.MakePayment(amount)
}
type AmazonAdapter struct {
Amazon *Amazon
}
func (a *AmazonAdapter) ProcessPayment(amount float32) bool {
return a.Amazon.PayAmazon(amount)
}
// main.go
func main() {
paymentGateway := &PayPalAdapter{
PayPal: &PayPal{},
}
paymentGateway2 := &AmazonAdapter{
Amazon: &Amazon{},
}
// triggers PayPal
paymentGateway.ProcessPayment(100)
// triggers Amazon
paymentGateway2.ProcessPayment(100)
}
In this example, we have a PaymentGateway
interface that needs to be implemented by all the payment providers. The PayPal
provider has its own unique interface, so we create an adapter, PayPalAdapter
, that implements the PaymentGateway
interface and wraps the PayPal
interface. Now, we can use the PayPal
provider with the payment gateway without changing the payment gateway's implementation. We can also imagine to add an adapter for Amazon
, which has its own API implemented in Amazon
.
Conclusion
In conclusion, the Adapter Pattern is a useful pattern in Golang when you have two incompatible interfaces that need to work together. It creates a wrapper object that can translate requests between both interfaces. The real-world example of a payment gateway shows how we can use this pattern to create adapters for different payment providers without changing the payment gateway's implementation.
References
- “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm