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.
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.
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 adapter will inherit both the targeted class and adaptee 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 the adapter will implement the targeted interface and wrap the adaptee class for compatibility. object adapter can be implemented in all popular programming languages.
It is time to see the adapter in action
import abc class StandardPGInterface(metaclass=abc.ABCMeta): @abc.abstractmethod def process_payment(self, amount, *args, **kwargs): raise NotImplemented class PayPalPG: def initiate_payment(self, amount): # Implement Payment code pass class StaxPG: def make_payment(self, amount): # Implement Payment code pass class PayPalPGAdapter(StandardPGInterface): def __init__(self): self.paypal = PayPalPG() def process_payment(self, amount): self.paypal.initiate_payment(amount=amount) 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)
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.
- 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.
- lets a single adapter have many adaptees, that is the adaptee and all of its subclasses.
- 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.
- Overall code complexity increases because of new interfaces and classes.
- "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