Design Patterns 1 - Adapter Pattern

Feb 7, 2021 00:00 · 503 words · 3 minute read DesignPatterns

Adapter design pattern helps you unify almost all classes.

1. Why design patterns?

Being good at problem-solving is one thing but to take your programming skill to the next level, one must know how complex software projects are architected. Software design patterns provide templates and tricks used to design and solve recurring software problems and tasks. Being well-versed in knowledge of design patterns allows one to spot brittle and immature code from miles away. There are three common design pattern types:

  • Creational design patterns: all about class instantiation or object creation. Class-creation patterns use inheritance effectively in the instantiation process, object-creation patterns use delegation effectively to get the job done.
  • Structural design patterns: organize different classes and objects to form larger structures and provide new functionality.
  • Behavioral design patterns: identify common communication patterns between objects and realize these patterns.

2. What is adapter pattern?

The adapter pattern is a structural design pattern that helps us make two incompatible interfaces compatible. In the following example, we created three classes Club, Musician and Dancer with different methods. Eventually, we’d like to call them through a single class Adapter. This is done by an important usage of class self.__dict__.update(), which udpates the functions within the class.

3. Code example

The UML digram of this example is as follow:

class Club:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'the club {self.name}'

    def organize_event(self):
        return 'hires an artist to perform for the people'

class Musician:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'the musician {self.name}'

    def play(self):
        return 'plays music'

class Dancer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'the dancer {self.name}'

    def dance(self):
        return 'does a dance performance'

class Adapter:
    def __init__(self, obj, adapted_methods):
        self.obj = obj
        self.__dict__.update(adapted_methods)

    def __str__(self):
        return str(self.obj)

# Main function

objects = [Club('Jazz Cafe'), Musician('Roy Ayers'), Dancer('Shane Sparks')]

for obj in objects:
    if hasattr(obj, 'play') or hasattr(obj, 'dance'):
        if hasattr(obj, 'play'):
            adapted_methods = dict(organize_event=obj.play)
        elif hasattr(obj, 'dance'):            
            adapted_methods = dict(organize_event=obj.dance)

        # referencing the adapted object here
        obj = Adapter(obj, adapted_methods)

    print(f'{obj} {obj.organize_event()}')

# Output: 

    the club Jazz Cafe hires an artist to perform for the people
    the musician Roy Ayers plays music
    the dancer Shane Sparks does a dance performance

As the above code shows the Adapter class unifies all other three classes even they have different subfunction within the classes.

4. Code explanation

Note that the self.__dict__() function includes all the attributes or functions within the corresponding class as a dictionary. Updating the dictionary corresponds to update the class. In above code, the last obj represents the Dancer class. Therefore, the self.obj is Dancer, and the self.organize_event is Dancer.dance.

obj.__dict__
{'obj': <__main__.Dancer at 0x10ecd12d0>,
 'organize_event': <bound method Dancer.dance of <__main__.Dancer object at 0x10ecd12d0>>}

The __str__ is used to return the corresponding string when call print(obj), such as:

obj
<__main__.Adapter at 0x10ecd1590>
print(obj)
the dancer Shane Sparks

Lastly, the dict(organize_event=obj.play) is same as {'organize_event': obj.play}. For example,

dict(a=2)
{'a': 2}

Reference:

tweet Share

Related Articles