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.
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:
Implementation Steps:
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()
PythonOutput:
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
PlaintextStep #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()
PythonOutput:
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
PlaintextExamples
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]
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
PythonFileOperation Class [pre-existing, implements File Interface]
# 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}")
PythonAPI Interface
# Api interface
class Api(ABC):
@abstractmethod
def fetch_data(self) -> str:
pass
@abstractmethod
def send_data(self, data: str) -> None:
pass
PythonNative API Class [implements Api interface]
# 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}")
PythonThird-Party API Class [implements Api interface]
# 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}")
PythonFile Adapter
We want to adapt the “FileOp” to the new interface “Api“.
# 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)
PythonDemo
# 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()
PythonOutput
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
PlaintextExample #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
PythonPlane 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
PythonHelicopter 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
PythonTransport 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
PythonBus 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
PythonBike 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
PythonAirTransportAdapter Class [implements Transport]
We want the “AirTransport” interface to adapt to the general “Transport” interface.
# 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()
PythonDemo
# 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()
PythonOutput
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
PlaintextSource 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.