In this post, an adapter pattern is explained. Original code in Java can be found in a book, I “増補改訂版Java言語で学ぶデザインパターン入門”.
An adapter works as a middleware between an adaptee and a target, each has its own methods. If an adaptee and a target are incompatible with each other, an adapter bridges a gap between the two objects. We could achieve this in 2 ways; 1. adapter by inheritance, 2. adapter by delegation.
Let’s see the concrete examples.
First, we create an adapter by inheritance. In this program, Banner class is defined as an adaptee, Printing interface as a target and PrintBanner as an adapter.
Banner is a provided class that has two methods to modify and print strings. Printing interface is an interface that a client uses and that has two abstract methods to modify and print strings.
# banner.py
class Banner:
def __init__(self, string):
self.string = string
def show_with_paren(self):
print(f"( {self.string} )")
def show_with_aster(self):
print(f"* {self.string} *")
# printing.py
from abc import ABC, abstractmethod
class Printing(ABC):
@abstractmethod
def print_weak(self):
pass
@abstractmethod
def print_strong(self):
pass
PrintBanner class extends Banner class and implements Printing Interface.
# print_banner.py
from banner import Banner
from printing import Printing
class PrintBanner(Printing, Banner):
def __init__(self, string):
super().__init__(string)
def print_weak(self):
super().show_with_paren()
def print_strong(self):
super().show_with_aster()
Let’s call PrintBanner class. It is clear that an abstraction works in a way that it hides an existence of Banner class. It means that we can change an adaptee and the program still works correctly without changing its internal methods or implementation as long as an adapter is properly set (we only need to change an adapter) .
# main.py
from print_banner import PrintBanner
def main():
p = PrintBanner("Hello")
assert isinstance(p, Printing)
p.print_weak()
p.print_strong()
if __name__ == "__main__":
main()
( Hello )
* Hello *
That was an adapter design by inheritance. Now let’s define an adapter by delegation.
In this way, we create an instance of an adaptee in an adapter and let an adaptee execute its own methods as opposed to extending an adaptee and calling methods.
# printing.py
from abc import ABC, abstractmethod
class Printing(ABC):
@abstractmethod
def print_weak(self):
pass
@abstractmethod
def print_strong(self):
pass
# print_banner.py
from printing import Printing
from banner import Banner
class PrintBanner(Printing):
def __init__(self, string):
self.banner = Banner(string)
def print_weak(self):
self.banner.show_with_paren()
def print_strong(self):
self.banner.show_with_aster()
One benefit of an adapter pattern: we do no need to modify existing code in an adaptee class or module when a new interface or API is needed. If we are sure that an adaptee is fully-tested and works properly, we do not want to change it since it may cause some bugs. We can only add an adapter so that an adaptee can communicate with an interface properly.