Skip to content

Adapter Design Pattern

Video Lecture

Section Video Links
Adapter Overview Adapter Overview Adapter Overview Adapter Overview 
Adapter Use Case Adapter Use Case Adapter Use Case Adapter Use Case 
Python isinstance() Function Python isinstance() Function Python isinstance() Function Python isinstance() Function 
Python time Module Python time Module Python time Module Python time Module 

Overview

...Refer to Book, Videos or Medium Membership for extra content.

Terminology

...Refer to Book, Videos or Medium Membership for extra content.

Adapter UML Diagram

Adapter Pattern UML Diagram

Source Code

...Refer to Book, Videos or Medium Membership for extra content.

./adapter/adapter_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# pylint: disable=too-few-public-methods
"Adapter Concept Sample Code"
from abc import ABCMeta, abstractmethod

class IA(metaclass=ABCMeta):
    "An interface for an object"
    @staticmethod
    @abstractmethod
    def method_a():
        "An abstract method A"

class ClassA(IA):
    "A Sample Class the implements IA"

    def method_a(self):
        print("method A")

class IB(metaclass=ABCMeta):
    "An interface for an object"
    @staticmethod
    @abstractmethod
    def method_b():
        "An abstract method B"

class ClassB(IB):
    "A Sample Class the implements IB"

    def method_b(self):
        print("method B")

class ClassBAdapter(IA):
    "ClassB does not have a method_a, so we can create an adapter"

    def __init__(self):
        self.class_b = ClassB()

    def method_a(self):
        "calls the class b method_b instead"
        self.class_b.method_b()

# The Client
# Before the adapter I need to test the objects class to know which
# method to call.
ITEMS = [ClassA(), ClassB()]
for item in ITEMS:
    if isinstance(item, ClassB):
        item.method_b()
    else:
        item.method_a()

# After creating an adapter for ClassB I can reuse the same method
# signature as ClassA (preferred)
ITEMS = [ClassA(), ClassBAdapter()]
for item in ITEMS:
    item.method_a()

Output

1
2
3
4
5
python ./adapter/adapter_concept.py
method A
method B
method A
method B

Example Use Case

...Refer to Book, Videos or Medium Membership for extra content.

Example UML Diagram

Adapter Pattern in Context

Source Code

./adapter/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"Adapter Example Use Case"

import time
import random
from cube_a import CubeA
from cube_b_adapter import CubeBAdapter

# client
TOTALCUBES = 5
COUNTER = 0
while COUNTER < TOTALCUBES:
    # produce 5 cubes from which ever supplier can manufacture it first
    WIDTH = random.randint(1, 10)
    HEIGHT = random.randint(1, 10)
    DEPTH = random.randint(1, 10)
    CUBE = CubeA()
    SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH)
    if SUCCESS:
        print(
            f"Company A building Cube id:{id(CUBE)}, "
            f"{CUBE.width}x{CUBE.height}x{CUBE.depth}")
        COUNTER = COUNTER + 1
    else:  # try other manufacturer
        print("Company A is busy, trying company B")
        CUBE = CubeBAdapter()
        SUCCESS = CUBE.manufacture(WIDTH, HEIGHT, DEPTH)
        if SUCCESS:
            print(
                f"Company B building Cube id:{id(CUBE)}, "
                f"{CUBE.width}x{CUBE.height}x{CUBE.depth}")
            COUNTER = COUNTER + 1
        else:
            print("Company B is busy, trying company A")
    # wait some time before manufacturing a new cube
    time.sleep(1)

print(f"{TOTALCUBES} cubes have been manufactured")

./adapter/cube_a.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# pylint: disable=too-few-public-methods
"A Class of Cube from Company A"
import time
from interface_cube_a import ICubeA

class CubeA(ICubeA):
    "A hypothetical Cube tool from company A"
    # a static variable indicating the last time a cube was manufactured
    last_time = int(time.time())

    def __init__(self):
        self.width = self.height = self.depth = 0

    def manufacture(self, width, height, depth):
        self.width = width
        self.height = height
        self.depth = depth
        # if not busy, then manufacture a cube with dimensions
        now = int(time.time())
        if now > int(CubeA.last_time + 1):
            CubeA.last_time = now
            return True
        return False  # busy

./adapter/cube_b.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pylint: disable=too-few-public-methods
"A Class of Cube from Company B"
import time
from interface_cube_b import ICubeB

class CubeB(ICubeB):
    "A hypothetical Cube tool from company B"
    # a static variable indicating the last time a cube was manufactured
    last_time = int(time.time())

    def create(self, top_left_front, bottom_right_back):
        now = int(time.time())
        if now > int(CubeB.last_time + 2):
            CubeB.last_time = now
            return True
        return False  # busy

./adapter/cube_b_adapter.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# pylint: disable=too-few-public-methods
"An adapter for CubeB so that it can be used like Cube A"
from interface_cube_a import ICubeA
from cube_b import CubeB

class CubeBAdapter(ICubeA):
    "Adapter for CubeB that implements ICubeA"

    def __init__(self):
        self.cube = CubeB()
        self.width = self.height = self.depth = 0

    def manufacture(self, width, height, depth):
        self.width = width
        self.height = height
        self.depth = depth

        success = self.cube.create(
            [0-width/2, 0-height/2, 0-depth/2],
            [0+width/2, 0+height/2, 0+depth/2]
        )
        return success

./adapter/interface_cube_a.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# pylint: disable=too-few-public-methods
"An interface to implement"
from abc import ABCMeta, abstractmethod

class ICubeA(metaclass=ABCMeta):
    "An interface for an object"
    @staticmethod
    @abstractmethod
    def manufacture(width, height, depth):
        "manufactures a cube"

./adapter/interface_cube_b.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# pylint: disable=too-few-public-methods
"An interface to implement"
from abc import ABCMeta, abstractmethod

class ICubeB(metaclass=ABCMeta):
    "An interface for an object"
    @staticmethod
    @abstractmethod
    def create(top_left_front, bottom_right_back):
        "Manufactures a Cube with coords offset [0, 0, 0]"

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
python ./adapter/client.py
Company A is busy, trying company B
Company B is busy, trying company A
Company A is busy, trying company B
Company B is busy, trying company A
Company A building Cube id:2968196317136, 2x3x7
Company A is busy, trying company B
Company B building Cube id:2968196317136, 8x2x8
Company A building Cube id:2968196317040, 4x6x4
Company A is busy, trying company B
Company B is busy, trying company A
Company A building Cube id:2968196317136, 5x4x8
Company A is busy, trying company B
Company B building Cube id:2968196317136, 2x2x9
5 cubes have been manufactured

New Coding Concepts

Python isinstance() Function

Syntax: isinstance(object, type)

Returns: True or False

You can use the inbuilt function isinstance() to conditionally check the type of an object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> isinstance(1,int)
True
>>> isinstance(1,bool)
False
>>> isinstance(True,bool)
True
>>> isinstance("abc",str)
True
>>> isinstance("abc",(int,list,dict,tuple,set))
False
>>> isinstance("abc",(int,list,dict,tuple,set,str))
True

You can also test your custom classes.

1
2
3
4
5
6
7
class my_class:
    "nothing to see here"

CLASS_A = my_class()
print(type(CLASS_A))
print(isinstance(CLASS_A, bool))
print(isinstance(CLASS_A, my_class))

Outputs

1
2
3
<class '__main__.my_class'>
False
True

You can use it in logical statements as I do in adapter_concept.py above.

Python time Module

The time module provides time related functions which are outlined in more detail at https://docs.python.org/3/library/time.html

In the above examples, I used the time module to make the process sleep for a second at a time, and to also measure the time difference between events that happened during the execution of the code.

In ./adapter/cube_a.py, I check the time.time() at various intervals to compare how long a task took.

1
2
3
4
    now = int(time.time())
    if now > int(CubeA.last_time + 1):
        CubeA.last_time = now
        return True

I also use the time module to sleep for a second between loops to simulate a 1 second delay. See ./adapter/client.py

1
2
    # wait some time before manufacturing a new cube
    time.sleep(1)

When executing ./adapter/cube_a.py you will notice that the process will run for about 10 seconds outputting the gradual progress of the construction of each cube.

Below, are some tests that you can try using the Python interactive console.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
>>> import time
>>> time.sleep(1)
>>> time.sleep(5)
>>> time.time()
1615536263.5662735
>>> time.time()
1615536276.2702708
>>> ts = time.time()
>>> ts
1615536294.062173
>>> time.time() - ts
22.64847731590271
>>> type(time.time())
<class 'float'>
>>> int(time.time())
1615536310
>>> time.ctime()
'Fri Mar 12 08:07:13 2021'
>>> time.ctime(0)
'Thu Jan  1 00:00:00 1970'
>>> time.ctime(1615536389)
'Fri Mar 12 08:06:29 2021'
>>> time.strftime("%a %d %b %Y %H:%M:%S")
'Fri 12 Mar 2021 08:08:49'
>>> time.strftime("%a %d %b %Y %H:%M:%S", time.gmtime())
'Fri 12 Mar 2021 08:09:49'
>>> time.gmtime()
time.struct_time(tm_year=2021, tm_mon=3, tm_mday=12, tm_hour=8, tm_min=16, tm_sec=51, tm_wday=4, tm_yday=71, tm_isdst=0)
>>> time.gmtime()[0]
2021
>>> time.gmtime()[2]
12
>>> time.gmtime().tm_wday
4
>>> time.strftime("%a %d %b %Y %H:%M:%S", time.gmtime(ts))
'Fri 12 Mar 2021 08:04:54'
>>> time.localtime()
time.struct_time(tm_year=2021, tm_mon=3, tm_mday=12, tm_hour=8, tm_min=20, tm_sec=59, tm_wday=4, tm_yday=71, tm_isdst=0)
>>> time.strftime("%a %d %b %Y %H:%M:%S", time.localtime())
'Fri 12 Mar 2021 08:21:20'

On linux, you can change the timezone of the current python session.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import time, os
>>> time.timezone
0
>>> time.tzname
('UTC', 'UTC')
>>> time.strftime("%a %d %b %Y %H:%M:%S", time.localtime(123456.789))
'Fri 02 Jan 1970 10:17:36'
>>> os.environ['TZ'] = 'America/New_York'
>>> time.tzset()
>>> time.tzname
('EST', 'EDT')
>>> time.timezone
18000
>>> time.strftime("%a %d %b %Y %H:%M:%S%z", time.localtime(123456.789))
'Fri 02 Jan 1970 05:17:36+0000'

Visit https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of supported time zones.

On Windows, a different method is used to change time, but be aware that it will change your servers system time globally and may affect any applications you are currently running.

1
2
3
python
>>> import os
>>> os.system('tzutil /s "Central Standard Time"')

See https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones for supported time zone strings.

On Windows, there is a system command tzutil that can be run from the command line.

To get your system time zone.

1
2
tzutil /g
> GMT Standard Time

To get a list of supported time zones

1
tzutil /l

To change the system time zone

1
tzutil /s "Tasmania Standard Time"

Summary

...Refer to Book, Videos or Medium Membership for extra content.