5. Adapter Pattern#

5.1. Introduction#

The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together by providing a wrapper or adapter that translates the interface of one class to another. This pattern is useful when you want to use an existing class in a system with a different interface.

5.2. Motivation#

In software development, there are scenarios where a system expects a certain interface, but you want to use an existing class with a different interface. The Adapter Pattern addresses this by providing a way to adapt the existing class to the expected interface, enabling interoperability between the two.

5.3. Implementation#

Let’s dive into the implementation of the Adapter Pattern. In this pattern, we define the target interface that the client expects, and an adapter class that implements the target interface and delegates calls to the existing class.

We define a TargetInterface class with a method request, representing the expected interface.

class TargetInterface:
    def request(self):
        pass

We define an Adaptee class with a method specific_request, which has a different interface from what the client expects.

class Adaptee:
    def specific_request(self):
        return "Response from Adaptee"

And, finally, we define an Adapter class that implements the TargetInterface and delegates calls to the Adaptee class, translating the interface.

class Adapter(TargetInterface):
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def request(self):
        return self.adaptee.specific_request()

5.3.1. Example#

Now, let’s see how we can use the Adapter Pattern in a real-life example of adapting a European electrical socket (Adaptee) to work with a US plug (TargetInterface).

We define a EuropeanSocket class representing a European electrical socket that provides European power.

class EuropeanSocket:
    def provide_european_power(self):
        return "European power"

We define a USPlug class representing the interface expected by a US plug.

class USPlug:
    def request_power(self):
        pass

And, now, we define a SocketAdapter class that implements the USPlug interface and adapts the EuropeanSocket to provide US power by converting the European power.

class SocketAdapter(USPlug):
    def __init__(self, european_socket):
        self.european_socket = european_socket

    def request_power(self):
        # Adapting European power to US power
        european_power = self.european_socket.provide_european_power()
        return f"Converted {european_power} to US power"
european_socket = EuropeanSocket()
socket_adapter = SocketAdapter(european_socket)

# Using the adapter to request power for a US plug
power = socket_adapter.request_power()
print(power)
Converted European power to US power

5.4. Benefits & Drawbacks#

Benefits

  • Allows incompatible interfaces to work together, enabling interoperability.

  • Promotes code reuse by adapting existing classes to new interfaces.

  • Provides a flexible way to integrate new classes into existing systems.

Drawbacks

  • Can add complexity to the code, especially when there are multiple levels of adaptation.

  • May introduce performance overhead due to the extra layer of abstraction.