The Adapter Pattern
Today we’ll discover a powerful design pattern—The Adapter Pattern. Adapters allow us to connect two incompatible interfaces—like the adapters we use to plug devices with foreign power plugs into our home’s power points. I cannot overstate the importance of adapters—without them, we could not build pluggable software architectures.
Imagine we want to connect and enable payments from a business logic Payment Module. We have decided to use PayPal as the payment provider. Having downloaded the PayPal SDK (Software Development Kit), at first, it may seem like a good idea to call the PayPal code directly from our Payment Module.
However, before we start programming, our Product Owner informs us that in the future, the Payment Module must also support payments from Stripe, another payment provider.
How do we call Stripe from the Payment Module if we are also are calling PayPal? A seemingly simple solution might be to use a
switch statement on payment provider—if it’s PayPay, call the PayPal code, otherwise pay via Stripe.
Paying with a third payment provider, say Worldpay, would seem to be as simple as including another
case statement into the
switch. However, adding more payment providers makes the
switch statement more challenging to maintain. Furthermore, each time we extend the
switch statement for yet another payment provider, we modify existing code rather than write new code—a clear violation of the Open-Closed Principle.
In this way, our Payments Module becomes more knowledgeable about the various payment providers—referencing the Paypal, Stripe and Worldpay SDK libraries.
Is there a better way? Can we avoid modifying our Payment Module and isolate code to connect each new payment provider?
One of the problems we face is that there is no commonality between the interfaces of the PayPal SDK code, the Stripe SDK code and the Worldpay SDK code—they are all different. We would need to remove these differences, abstract them away somehow.
Maybe we could create a “plug” on top of the different SDKs? Could each plug be designed to allow the Payment Module to call a specific payment provider in a unified way? And these plugs, or adapters, would accept calls from the Payment Module and undertake the necessary conversions to pass through those calls to the payment providers.
The Payment Module would not need to know which payment provider it is using. That is a crucial property. It’s what gives us the flexibility to unplug PayPal and replace it with Worldpay or Stripe.
Extra: Here is a previous article on removing a
switch statement and replacing it dynamically with a pluggable implementation of a payment provider. Each of the
StripePaymentGateway, is an adapter.