Software Architecture; When and How to Use the Adaptor Pattern (with examples in PHP)
In the world of electronics (at least if you’re as old as me) everyone is familiar with the solution to a cable that doesn’t fit, an adaptor! Sometimes more commonly called a ‘dongle’ in modern vernacular.
This is a common pattern in the software world as well, but perhaps one that isn’t always recognized immediately. A good way to think of the adaptor pattern, is that it is the solution to building a set of functionality that leverages an external dependency (API, hardware, code written by another team, etc.) and abstracting the implementation of the integration details.
Let’s dive into a practical example of how to apply the Adaptor pattern in PHP.
Example: Integrating a Payment Gateway
Suppose you are developing an e-commerce website and need to integrate a payment gateway to process payments. You’ve chosen two popular payment gateways: PayPal and Stripe. However, each gateway has a different API, and you want to keep your code maintainable and flexible to support future payment gateways.
First, let’s define the target interface that represents a payment gateway:
interface PaymentGateway
{
public function charge($amount);
public function refund($transactionId, $amount);
}
Now, let’s create classes for PayPal and Stripe with their respective APIs:
class PayPal
{
public function executePayment($amount)
{
// Code to charge using PayPal API
}
public function issueRefund($transactionId, $amount)
{
// Code to refund using PayPal API
}
}
class Stripe
{
public function createCharge($amount)
{
// Code to charge using Stripe API
}
public function refundCharge($transactionId, $amount)
{
// Code to refund using Stripe API
}
}
As you can see, the method names and signatures are different for each payment gateway. To make them compatible with our PaymentGateway
interface, we'll create adaptors for each gateway:
class PayPalAdaptor implements PaymentGateway
{
private $paypal;
public function __construct(PayPal $paypal)
{
$this->paypal = $paypal;
}
public function charge($amount)
{
$this->paypal->executePayment($amount);
}
public function refund($transactionId, $amount)
{
$this->paypal->issueRefund($transactionId, $amount);
}
}
class StripeAdaptor implements PaymentGateway
{
private $stripe;
public function __construct(Stripe $stripe)
{
$this->stripe = $stripe;
}
public function charge($amount)
{
$this->stripe->createCharge($amount);
}
public function refund($transactionId, $amount)
{
$this->stripe->refundCharge($transactionId, $amount);
}
}
Now, our adaptors implement the PaymentGateway
interface, and our application can use any payment gateway without worrying about the differences in their APIs:
function processPayment(PaymentGateway $gateway, $amount)
{
$gateway->charge($amount);
}
$paypal = new PayPal();
$stripe = new Stripe();
$paypalAdaptor = new PayPalAdaptor($paypal);
$stripeAdaptor = new StripeAdaptor($stripe);
// Using PayPal
processPayment($paypalAdaptor, 100);
// Using Stripe
processPayment($stripeAdaptor, 100);
In this example, we’ve implemented the Adaptor pattern to seamlessly integrate PayPal and Stripe payment gateways into our application. By using adaptors, we can easily switch between different payment gateways or add new ones without modifying the existing code.
This may be an obvious example of how to use the adaptor pattern, but I encourage you to use this pattern liberally. It is particularly strong when building interfaces into code managed by other teams, or even other developers!