Introduction
The adapter Design pattern also known as the wrapper design pattern converts the interface of a class into another interface the client expects. Adapters let classes work together that couldn't otherwise because of incompatible interfaces. If the adaptee changes over time the adapter should also encapsulate those changes, This ensures that the client doesn't require modification whenever it needs to interact with a different interface.
It is easy to understand because we frequently see them in our day-to-day lives. Imagine you are traveling to a foreign country and you need to charge your electronic devices but the electronic outlet in the foreign country uses a different plug type and voltage compared to your home country. In this situation, you need an adapter to bridge the gap between your device's plug and the foreign country's outlet. The adapter allows you to connect your device to the foreign electrical system, ensuring that it receives the correct voltage and fits into the different plug types.
Problem
Assume you are hired as a developer in an E-commerce site and now you are responsible for integrating multiple payment gateway in your system like PayPal, Stax, etc also if necessary the product owner would like to add some more payment gateway in his system. However, each payment gateway has a different interface for payment processing. but we need a standardized interface for processing payments. It is possible to solve this problem without a standardized interface. but this will make the codebase messy and less future-proof.
Solution
To address this problem we can use the adapter design pattern. we will implement an adapter for each payment gateway. These adapters will bridge the gap between the diverse payment gateway interface and your standard payment processing interface.
Now to implement the adapter design pattern there are two kinds of adapter based on inheritence and object composition.
Class Adapter: The class adapter is based on multiple inheritance. where each payment gateway adapter will inherit both the standard payment gateway interface and its payment gateway class and override the behavior of the payment gateway. to make the interface compatible with the client interface. class adapter implementation is only possible in programming languages that support multiple inheritance like C++, Python, etc.
Object Adapter: The object adapter makes use of the object composition principle. where each payment gateway adapter will implement the standard payment gateway interface and wrap its payment gateway class. object adapter can be implemented in all popular programming languages.
Figure 1: Class Adapter
Figure 2: Object Adapter
Sample Code
It is time to see the adapter in action
import abc
# Standard payment gateway interface
class StandardPGInterface(metaclass=abc.ABCMeta):
@abc.abstractmethod
def process_payment(self, amount, *args, **kwargs):
raise NotImplemented
# PayPal payment gateway
class PayPalPG:
def initiate_payment(self, amount, currency):
# Implement Payment code
pass
# Stax Payment Gateway
class StaxPG:
def make_payment(self, amount):
# Implement Payment code
pass
# PayPal Payment gateway adapter
class PayPalPGAdapter(StandardPGInterface):
def __init__(self):
self.paypal = PayPalPG()
def process_payment(self, amount, currency):
self.paypal.initiate_payment(amount=amount, currency=currency)
# Stax Payment Gateway Adapter
class StaxPGAdapter(StandardPGInterface):
def __init__(self):
self.stax = StaxPG()
def process_payment(self, amount):
self.stax.make_payment(amount=amount)
if __name__ == "__main__":
paypal = PayPalPGAdapter()
paypal.process_payment(amount=100)
stax = StaxPGAdapter()
stax.process_payment(amount=100)
Applicability
Use the adapter pattern when:
You want to use an existing class but its interface does not match the one you need.
You aim to develop a reusable class that can collaborate with unrelated and unforeseen classes.
(object adapter only) You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.
The trade-off between class and object adapter
Class Adapter:
Class adapter won't work when we want to adapt a class and all its subclasses.
Class adapters can override the behavior of the adaptee. since the class adapter is a subclass of adaptee.
Object Adapter:
- lets a single adapter have many adaptees, that is the adaptee and all of its subclasses.
Pros and Cons
Pros
Maintain Single Responsibility Principle. You can divide the interface from the main business logic of the program.
Maintain Open/Closed Principle. You can introduce new types of adapters without breaking the client code.
Cons
- Overall code complexity increases because of new interfaces and classes.
Resources
"Head First Design Patterns" by Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson, https://www.amazon.com/Head-First-Design-Patterns-Brain-Friendly/dp/0596007124
"Design Patterns: Elements of Reusable Object-Oriented Software" by Gamma Erich, Helm Richard, Johnson Ralph, Vlissides John, https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8
Refactoring Guru article on Adapter Design Pattern, https://refactoring.guru/design-patterns/adapter