## Adapter Pattern ### Problem As the system evolves, you'll likely encounter interfaces of instances that are incompatible with their intended clients. These interfaces do perform the necessary behavior, but maybe the method names are just different. This happens quite a lot since the interface of the dependency may be originally built for different reason. The interface may be an external module imported on existing client code. You can just change the incompatible interface to support the functionality you need but this is not always possible and may introduce code duplication. ![adapter](https://raw.githubusercontent.com/HowDoIGitHelp/CMSC23MDNotes/master/Markdown%20Lecture%20Notes%20and%20Lab%20Exercises/copyright%20free%20drawings/Adapter.png) ### Solution In the same way a usb-c interface is usable on a usb 2.0 using an adapter, you can use an incompatible service on a client as a compatible instance using the adapter pattern. Say you have an instance of `AbstractService` (it could be any realization of `AbstractService`), that needs to be used like an instance of `RequiredInterface` by some client. What you need to do is to create an adapter to `AbstractService` called `ServiceAdapter` which realizes `RequiredInterface`. To adapt the instance of `AbstractService`, you have to compose it inside the `ServiceAdapter`. So that `serviceMethod1()` is adapted to `method1()`. ![adapter](https://raw.githubusercontent.com/HowDoIGitHelp/CMSC23MDNotes/master/Markdown%20Lecture%20Notes%20and%20Lab%20Exercises/uml/adapter.svg) Whenever, a `ServiceAdapter` calls `method1()` it instead delegates the behavior to the embedded service, which instead calls `serviceMethod1()` ```python from abc import ABC, abstractmethod class AbstractService(ABC): @abstractmethod def serviceMethod1(self): pass @abstractmethod def serviceMethod2(self,data:str): pass class RealService(AbstractService): def serviceMethod1(self): print("method1 of RealService") def serviceMethod2(self,data:str): print("method2 of RealService with "+ data) class RequiredInterface(ABC): @abstractmethod def method1(self): pass @abstractmethod def method2(self,data:str): pass class ServiceAdapter(RequiredInterface): def __init__(self, service:AbstractService): self.__service = service def method1(self): self.__service.serviceMethod1() def method2(self,data:str): self.__service.serviceMethod2(data) s:AbstractService = RealService() a:RequiredInterface = ServiceAdapter(s) a.method1() a.method2("foo") ``` ### Why this is elegant - **Open/Closed Principle** - Instead of changing existing incompatible interfaces, you can extend them by creating adapters. - **Interface Segregation Principle** - Instead of cluttering up your code with duplicate functions and unused interface methods, you instead create adapters only when it is needed. ### How to implement it 1. If you want an `AbstractService` to be used like a `RequiredInterface`, Create a `ServiceAdapter` that realizes `RequiredInterface` and contains an attribute `service` that is a reference to an instance of `AbstractService`. 2. Inside `ServiceAdapter`, the implementations of `RequiredInterface`'s abstract methods are merely calls to the methods of the reference `service`.