Design Pattern: Adapter Pattern in Python

Adapter pattern is used to make two incompatible interfaces compatible and heavily used to accommodate functionality from an old(legacy) interface to a new interface.

The adapter works a layer on top of the old/incompatible interface and makes it to adapt a new interface.

Adapter Diagram
Adapter Diagram

NOTES

In this article, we discuss the implementation of the Adapter Pattern in Python.

See the Adapter in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Adapter Pattern-

Implementation

Here is then implementation use-case and implementation process of Adapter pattern-

Prerequities:

We have an legecy/old/pre-existing interface.
Now we have a new interface.
We want the legecy/old/pre-existing inteface, to adapt to the new interface.

Implementation Steps:

Create a class for the Adapter.
Implement the new interface(abstract class) in the Adapter class.
In the “__init__” method of Adapter, accept an object of the old interface, and save it in an attribute.
In the implementation of abstract methods of the Adapter, use/call the methods from the old interface object.

Step #1: Simple Implementation

Let’s start with a simple example to get a basic understanding of the Adapter pattern.

# Old system(with old interface)
class OldSystem:
    def old_op1(self) -> None:
        print("Operation 1 of Old System")

    def old_op2(self) -> None:
        print("Operation 2 of Old System")

    def old_op3(self) -> None:
        print("Operation 3 of Old System")


# New system(with new interface)
class NewSystem:
    def operation1(self) -> None:
        print("Operation 1 of New System")

    def operation2(self) -> None:
        print("Operation 2 of New System")


# Adapter -> adapt the OldSystem to NewSystem
class Adapter:
    def __init__(self, system) -> None:
        self.system = system

    def operation1(self) -> None:
        self.system.old_op1()

    def operation2(self) -> None:
        self.system.old_op2()
        self.system.old_op3()


# Demo usage
def main():
    # New system code
    new_system = NewSystem()
    new_system.operation1()
    new_system.operation2()

    # Old system with adapter
    old_system = OldSystem()

    adapted_obj = Adapter(old_system)
    adapted_obj.operation1()
    adapted_obj.operation2()


if __name__ == "__main__":
    main()
Python

Output:

New Operation 1 of New System
New Operation 2 of New System


Operation 1 of Old System
Operation 2 of Old System
Operation 3 of Old System
Plaintext

Step #2: Old and New Interface

Here is a proper example with old and new interface, how the Adapter pattern is implemented.

from abc import ABC, abstractmethod


# Old interface
class OldInterface(ABC):
    @abstractmethod
    def old_op1(self):
        pass

    @abstractmethod
    def old_op2(self):
        pass

    @abstractmethod
    def old_op3(self):
        pass


# Old interface implementation
class OldSystem(OldInterface):
    def old_op1(self):
        print("Operation 1 of Old System")

    def old_op2(self):
        print("Operation 2 of Old System")

    def old_op3(self):
        print("Operation 3 of Old System")


# New interface
class NewInterface(ABC):
    @abstractmethod
    def operation1(self):
        pass

    @abstractmethod
    def operation2(self):
        pass


# New interface implementation
class NewSystem(NewInterface):
    def operation1(self):
        print("New Operation 1 of New System")

    def operation2(self):
        print("New Operation 2 of New System")


# Adapter
class Adapter(NewInterface):
    def __init__(self, old_interface: OldInterface):
        self.old_interface = old_interface

    def operation1(self):
        self.old_interface.old_op1()

    def operation2(self):
        self.old_interface.old_op2()
        self.old_interface.old_op3()


# Demo usage
def main():
    new_system = NewSystem()
    new_system.operation1()
    new_system.operation2()

    old_system = OldSystem()

    adapted_obj = Adapter(old_system)
    adapted_obj.operation1()
    adapted_obj.operation2()


if __name__ == "__main__":
    main()
Python

Output:

New Operation 1 of New System
New Operation 2 of New System


Operation 1 of Old System
Operation 2 of Old System
Operation 3 of Old System
Plaintext

Examples

We have discussed 2 examples here, for the Adapter pattern usage.

Example #1: API with File Adapter

Here is the case in this example-

Existing/Old Interface

We have interface(abstract class) “FileOp“, and class “FileOperation” extends the abstract class. This class is responsible for file read/write operations.

New Interface(after new requirement)

In the next phase, we have introduced “Api” interface(abstract class) which is implemented by classes “NativeApi“, “ThirdPartyApi“. These are responsible for handling API requests.

Now we want to use the “FileOperation” the same way we use our API classes. For that the “FileOperation” (specifically “FileOp” interface) has to adapt to the “Api” interface.

File Interface [pre-existing]

Create class “FileOp“, inherit “ABC” to make it an abstract class.
Define abstract methods, as per requirement. Here we have methods – read_file, write_file.
from abc import ABC, abstractmethod


# File Operation interface
class FileOp(ABC):
    @abstractmethod
    def read_file(self) -> str:
        pass

    @abstractmethod
    def write_file(self, input: str) -> None:
        pass
Python

FileOperation Class [pre-existing, implements File Interface]

Create “FileOperation” class.
Inheirit from “FileOp“.
Implement the abstract methods – read_file, write_file.
# File operation class implementing File operation interface
class FileOperation(FileOp):
    def __init__(self, file_name: str) -> None:
        self.file_name = file_name

    def read_file(self) -> str:
        print(f"Reading from file: {self.file_name}")

        return "Some dummy response read from file"

    def write_file(self, input: str) -> None:
        print(f"Write to file {self.file_name}: {input}")
Python

API Interface

Declare class “Api“.
Declare abtract methods – fetch_data, send_data.
# Api interface
class Api(ABC):
    @abstractmethod
    def fetch_data(self) -> str:
        pass

    @abstractmethod
    def send_data(self, data: str) -> None:
        pass
Python

Native API Class [implements Api interface]

Create class “NativeApi“.
Inherit from “Api“.
Define methods – fetch_data, send_data as part of “Api” interface implementaiton.
# Native api class implementing Api interface
class NativeApi(Api):
    def __init__(self) -> None:
        self.host = "http://localhost"

    def fetch_data(self) -> str:
        print("Fetching dat from native api")

        return "data read from native api"

    def send_data(self, data: str) -> None:
        print(f"Sending data to Native API: {data}")
Python

Third-Party API Class [implements Api interface]

Create class “ThirdPartyApi“.
Inherit from “Api“.
Define methods – fetch_data, send_data as part of “Api” interface implementaiton.
# Third part api implementing the Api interface
class ThirdPartyApi(Api):
    def __init__(self, host: str) -> None:
        self.host = host

    def fetch_data(self) -> str:
        print(f"Fetching data from third part api: {self.host}")

        return "data from third part api"

    def send_data(self, data: str) -> None:
        print(f"Sending data to third party API: {data}")
Python

File Adapter

We want to adapt the “FileOp” to the new interface “Api“.

Define class “FileAdapter“.
Inherit from interface “Api“.
In the “__init__” method accept an object of type “FileOp” and save that to attribute “file_op“.
Define methods “fetch_data“, “send_data“, and in the implementation use/call the methods from “file_op” object, which calls the methods from the “FileOperation” object.
# Adapter to adapt the File opeation
# to the Api operations
class FileAdapter(Api):
    def __init__(self, file_op: FileOp) -> None:
        self.file_op = file_op

    def fetch_data(self) -> str:
        return self.file_op.read_file()

    def send_data(self, data: str) -> None:
        self.file_op.write_file(data)
Python

Demo

# Demo usage
def main():
    # make a call to third part API for testing
    third_part_api = ThirdPartyApi("https://somethirdpartapi.com")
    third_part_api.fetch_data()
    third_part_api.send_data("1234")

    # Make a call to the file via FileAdapter
    file_op = FileOperation("abc.txt")
    file_adapter = FileAdapter(file_op)
    file_adapter.fetch_data()
    file_adapter.send_data("ABCDEF")


if __name__ == "__main__":
    main()
Python

Output

Output will be as below-

Fetching data from third part api: https://somethirdpartapi.com
Sending data to third party API: 1234


Reading from file: abc.txt
Write to file abc.txt: ABCDEF
Plaintext

Example #2: Transport

Let’s consider an exmaple with transport/vehicles, which needs an adapter.

AirTransport Interface

Here we have an interface(abstract class) that implemented by all the air transports like- plane, helicopter, etc.

from abc import ABC, abstractmethod


# AirTransport Interface
class AirTransport(ABC):
    @abstractmethod
    def get_number_of_wheels(self) -> int:
        pass

    @abstractmethod
    def get_number_of_engines(self) -> int:
        pass

    @abstractmethod
    def get_weight(self) -> float:
        pass

    @abstractmethod
    def get_distance_travelled(self) -> float:
        pass

    @abstractmethod
    def get_travel_cost_total(self) -> float:
        pass
Python

Plane Class [implements AirTransport]

Plane class implements the “AirTransport” interface.

# Plane Class
class Plane(AirTransport):
    def get_number_of_wheels(self) -> int:
        return 3

    def get_number_of_engines(self) -> int:
        return 2

    def get_weight(self) -> float:
        return 127_000

    def get_distance_travelled(self) -> float:
        return 500

    def get_travel_cost_total(self) -> float:
        return 3_000
Python

Helicopter Class [implements AirTransport]

Helicopter class also implements the “AirTransport“.

# Helicopter Class
class Helicopter(AirTransport):
    def get_number_of_wheels(self) -> int:
        return 0

    def get_number_of_engines(self) -> int:
        return 1

    def get_weight(self) -> float:
        return 12_000

    def get_distance_travelled(self) -> float:
        return 180

    def get_travel_cost_total(self) -> float:
        return 20_000
Python

Transport Interface

Here we have a general “Transport” interface, intented to be implemented by all transports.

# Transport Interface
class Transport(ABC):
    @abstractmethod
    def get_number_of_wheels(self) -> int:
        pass

    @abstractmethod
    def get_weight(self) -> float:
        pass

    @abstractmethod
    def get_distance_travelled(self) -> float:
        pass

    @abstractmethod
    def get_travel_cost_per_mile(self) -> float:
        pass
Python

Bus Class [implements Transport]

# Bus Class
class Bus(Transport):
    def get_number_of_wheels(self) -> int:
        return 4

    def get_weight(self) -> float:
        return 10_000

    def get_distance_travelled(self) -> float:
        return 1_000

    def get_travel_cost_per_mile(self) -> float:
        return 5
Python

Bike Class [implements Transport]

# Bike Class
class Bike(Transport):
    def get_number_of_wheels(self) -> int:
        return 2

    def get_weight(self) -> float:
        return 700

    def get_distance_travelled(self) -> float:
        return 80

    def get_travel_cost_per_mile(self) -> float:
        return 4
Python

AirTransportAdapter Class [implements Transport]

We want the “AirTransport” interface to adapt to the general “Transport” interface.

Create class “AirTransportAdapter“.
Inherit from “Transport“.
In the “__init__” method accept a “AirTransport” object, and save that in attribute “air_transport“.
Define reqired method implementing the “Tranport” adapter, and in the method implementaitons use/ call methods from “air_transport“(old interface object).
# AirTransportAdapter Class
class AirTransportAdapter(Transport):
    def __init__(self, air_transport: AirTransport):
        self.air_transport = air_transport

    def get_number_of_wheels(self) -> int:
        return self.air_transport.get_number_of_wheels()

    def get_weight(self) -> float:
        return self.air_transport.get_weight()

    def get_distance_travelled(self) -> float:
        # Convert nautical miles to miles
        distance_in_nautical_mile = self.air_transport.get_distance_travelled()
        return distance_in_nautical_mile * 1.151

    def get_travel_cost_per_mile(self) -> float:
        total_cost = self.air_transport.get_travel_cost_total()
        return total_cost / self.get_distance_travelled()
Python

Demo

# Demo Usage
def main():
    print("Get information of Bus travel...")

    bus = Bus()
    print(f"Number of wheels: {bus.get_number_of_wheels()}")
    print(f"Weight (kg): {bus.get_weight()}")
    print(f"Distance (miles): {bus.get_distance_travelled()}")
    print(f"Cost per mile: {bus.get_travel_cost_per_mile()}")

    print("\nGet information of Plane travel...")

    plane_transport = AirTransportAdapter(Plane())
    print(f"Number of wheels: {plane_transport.get_number_of_wheels()}")
    print(f"Weight (kg): {plane_transport.get_weight()}")
    print(f"Distance (miles): {plane_transport.get_distance_travelled()}")
    print(f"Cost per mile: {plane_transport.get_travel_cost_per_mile()}")


if __name__ == "__main__":
    main()
Python

Output

Output will be as below-

Get information of Bus travel...
Number of wheels: 4
Weight (kg): 10000
Distance (miles): 1000
Cost per mile: 5


Get information of Plane travel...
Number of wheels: 3
Weight (kg): 127000
Distance (miles): 575.5
Cost per mile: 5.212858384013901
Plaintext

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check the implementation of adapter patterns in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.