Adapter Pattern in Java and Python
This article exists on YouTube
The entire content of this article is also available on YouTube. It's your choice: if you prefer video content, you can watch it on YouTube. If you prefer reading, feel free to continue here.
What is an Adapter Pattern?
The Adapter Pattern is a programming pattern used in software engineering and is part of the group of structural patterns. It may be used in any object-oriented programming language, such as Java, Python, C++, and so on. What it does is to enable incompatible interfaces of different classes to work together by creating a bridge between them. Methods in one class are connected to different methods in the other class.
There are two types of Adapter Pattern implementations named the Class Adapter and the Object Adapter. They both do the same, but their approach to it differs:
In this approach, the adapter class inherits from both the target interface and the adaptee class. It overrides methods from the target interface for implementation, and it delegates calls to the adaptee's methods that it can access due to them being extended over.
In this approach, the adapter class inherits only the target interface, but not the adaptee class. The Object Adapter also overrides the target interface methods for implementation. Instead of inheriting the adaptee class though, the adaptee class is accessed through a constructor parameter of the Object Adapter class and then stored internally. Any call to the adaptee class will be performed by accessing the local variable for the adaptee class.
So, what does the Adapter Pattern mean in practice? We as the client that wants to make use of the adaptee class, will not create an instance of it. Instead we will create an instance of the adapter. This also brings the advantage of loose coupling, because the client and the adaptee do not know about each other. They both use the adapter to talk to each other. In other words the adapter acts as a translator in a role to convert from one interface to another.
What is the structure of an Adapter Pattern?
-
Target Interface
The interface that the client code expects to interact with. Operations and behaviors that the client can use are defined here. The client can interact with the Target interface without the need to know about underlying implementations or the Adaptee. -
Adaptee
The existing interface or class that needs to be integrated into the system but has an incompatible interface with the client code. This is the entity that is adapted to work with the Target interface. -
Adapter
The class that bridges the gap between the Target interface and the Adaptee. It allows the Adaptee's interface to seamlessly work with the client code by adapting it to match the expected interface defined by the Target interface. In other words, the Adapter acts as a mediator between the client code and the Adaptee.
Example Codes
There are a number of programming languages where the Adapter Pattern can prove useful. Every language that supports the construct of classes can make use of this pattern.
All the codes within this article can also be found on the GitHub repository programming-patterns and in there within the directory Adapter Pattern.
Java Example
The following example is a Java application putting the Adapter Pattern into action. All the following files need to be placed into the same folder so that the code within the files can work properly, because the files also reference each other. Being the smallest possible code example, this little project consists of five Java files:
- Target.java
- Adaptee.java
- ClassAdapter.java
- ObjectAdapter.java
- Main.java
The first thing that needs to be defined is the target interface. Here we will define what methods we can to communicate to the client. Those are also the ones that the adapter needs to implement.
public interface Target {
public void request();
}
Then we need to define the adaptee. This is the class that is incompatible to the client and therefore requires the Adapter Pattern to communicate with the client.
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specific request.");
}
}
Now we need either the class adapter or the object adapter. Here we define what exactly will be trasmitted / translated.
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
this.adaptee.specificRequest();
}
}
And that is it already. The main construct of the Adapter Pattern consists of these five Java files. The only thing that remains is to apply their logic within the executing main class. The main(...) method creates an instance of the ClassAdapter and then calls the request() method. Right after an instance of Adaptee is created to act as parameter for the instance creation the ObjectAdapter right after that. After that the request() method is executed.
public class Main {
public static void main(String[] args) {
System.out.println("Adapter Pattern in Java");
System.out.println("-----------------------");
System.out.println();
// Using class adapter.
Target classAdapter = new ClassAdapter();
classAdapter.request();
// Using object adapter.
Adaptee adaptee = new Adaptee();
Target objectAdapter = new ObjectAdapter(adaptee);
objectAdapter.request();
}
}
There also needs to be a Manifest.txt file within the same folder. This is a metadata file used primarily in JAR files. It contains information about the JAR file itself and its contents. It is placed within thie META-INF folder within the JAR archive file structure.
Main-Class: Main
To compile this code into an executable Java application which would be a JAR file, a couple of commands need to be executed from within the same directory. The commands are found within the build file.
@echo off
javac *.java
jar cvfm AdapterDemo.jar Manifest.txt *.class
#!/bin/bash
javac *.java
jar cvfm AdapterDemo.jar Manifest.txt *.class
Now that the compilation has taken place, there are new files within the same directory. Every JAVA file has its equivalent CLASS file, and there is also a file called AdapterDemo.jar. This is the Java application that needs to be executed. The command for that is within the run file.
@echo off
java -jar AdapterDemo.jar
#!/bin/bash
java -jar AdapterDemo.jar
Testing the Java Example
Now that all the files are ready and within their correct location they can be put to test. First we need to compile the Java code and then we can execute it to see what it will print in the Windows Command Prompt or in the MacOS / Linux Terminal.
Python Example
The following example is a Python application putting the Adapter Pattern into action. All the following files need to be placed into the same folder so that the code within the files can work properly, because the files also reference each other. As this is the smallest possible example, this little project consists of six Python files:
- target.py
- adaptee.py
- class_adapter.py
- object_adapter.py
- main.py
The first thing that needs to be defined is the target class. Here we will define what methods we can to communicate to the client. Those are also the ones that the adapter needs to implement.
from abc import ABC
class Target(ABC):
def request(self):
pass
Then we need to define the adaptee. This is the class that is incompatible to the client and therefore requires the Adapter Pattern to communicate with the client.
class Adaptee:
def specificRequest(self):
print("Adaptee's specific request.")
Now we need either the class adapter or the object adapter. Here we define what exactly will be trasmitted / translated.
from adaptee import Adaptee
from target import Target
class ClassAdapter(Adaptee, Target):
def request(self):
self.specificRequest()
from target import Target
class ObjectAdapter(Target):
def __init__(self, adaptee):
self.adaptee = adaptee
def request(self):
self.adaptee.specificRequest()
And that is it already. The main construct of the Adapter Pattern consists of these five Python files. The only thing that remains is to apply their logic within the executing main class. The main() method creates an instance of the ClassAdapter and then calls the request() method. Right after an instance of Adaptee is created to act as parameter for the instance creation the ObjectAdapter right after that. After that the request() method is executed.
from class_adapter import ClassAdapter
from adaptee import Adaptee
from object_adapter import ObjectAdapter
def main():
print("Adapter Pattern in Python")
print("-------------------------")
print()
# Using class adapter.
classAdapter = ClassAdapter()
classAdapter.request()
# Using object adapter.
adaptee = Adaptee()
objectAdapter = ObjectAdapter(adaptee)
objectAdapter.request()
if __name__ == "__main__":
main()
There is no need to compile anything in Python. The code is executed directly using the python command under Windows and the python3 command under MacOS / Linux.
Testing the Python Example
Now that all the files are ready and within their correct location they can be put to test. Python does not need to be compiled before being run. Therefore we can execute it right away to see what it will print in the Windows Command Prompt or in the MacOS / Linux Terminal.
More Information
So, where do I have my information from? In general, there are lots of places on the internet that offer great information. But that is just one source of information. Another source would be books. Those have the advantage that usually several people work on the same book before it is released, raising the chance for distribution of good and proven information.
Head First - Design Patterns
Building Extensible and Maintainable Object-Oriented Software
A book that has proven to be a valuable asset within my personal library is Head First - Design Patterns by the publisher O'Reilly. It has a fun approach to this topic, having recurring characters, such as the Guru, the Developer, the Skeptical Developer, Joe, and many more.
Java Design Patterns
A Hands-On Experience with Real-World Examples
Another highly valuable book is Java Design Patterns: A Hands-On Experience with Real-World Examples by the publisher Apress. It offers a conservative and rather scientific approach to the subject. It also offers code examples in Java, although any object-oriented programming language applies to the design patterns shown here.