Some of the ‘GoF’ design patterns disappear in Python

note: if a module imports more than 3 major modules. see if it can be refactored.

‘program to interfaces, not implementation’

Java or C++ fixes the type of output naturally(eg. if File format, then WebSocket format will not be a valid output). So have to write an interface that fakes to be any types.

if/elif/else should be used only when these branches serve different goals. If found different branch serve the same goal but just different execution, look for a common interface.

5 creational patterns

Factory method, Abstract Factory and Singleton patterns is trivial in Python since a function in Python could possibly return any type of class.

Singleton pattern in fact is a alternate to global variable in other languages. But Python modules can not intercept global variables. So Python’s singletons is just function which looks like:

# If singleton class not set, set it. If set, return it
_singleton = None
def get_singleton(cls=MyClass):
    global _singleton
    if not _singleton:
        _singletone = cls()
    return _singleton

Factory

The mechanics of Factory Method are:
Client depends on one concrete implementation of an interface. Which concrete implementation to be used is returned and decided by creator component. Of course, every concrete implementation should be implemented, as methods or functions. So elements are:

  • client
  • creator/factory
  • concrete implementations

Here is the example of refactoring a class into factory pattern:

#we may endup here for the first versions
class SongSerializer(object):
    def serialize(self, song, format):
        if format == 'JSON':
            song_info = {
                'id': song.song_id,
                'title': song.title,
                'artist': song.artist
            }
            return json.dumps(song_info)
        if format == 'XML':
            song_info = et.Element('song', attrib={'id': song.song_id})
            title = et.SubElement(song_info, 'title')
            title.text = song.title
            artist = et.SubElement(song_info, 'artist')
            artist.text = song.artist
            return et.tostring(song_info, encoding='unicode')
        else:
            raise ValueError(format)

# refactor            
# step 1: move implements to interfaces            
class SongSerializer1(object):
    def serialize(self, song, format): #now this is the client code
        if format == 'JSON': return self._serialize_to_jons(song) #these are the interfaces
        if format == 'XML': return self._serialize_to_xml(song)
        else: raise ValueError(format)
            
    def _serialize_to_jons(self, song): #these are the concrete implementations
        payload = {
                'id': song.song_id,
                'title': song.title,
                'artist': song.artist
            }
        return json.dumps(payload)
    def _serialize_to_xml(self, song):
        song_info = et.Element('song', attrib={'id': song.song_id})
        title = et.SubElement(song_info, 'title')
        title.text = song.title
        artist = et.SubElement(song_info, 'artist')
        artist.text = song.artist
        return et.tostring(song_info, encoding='unicode')
#step 3: With common interface, we could send 'format' as a parameter to control the which concrete implementation to use.
#this is the central idea of Factory pattern.
class SongSerializer2(SongSerializer1): #methods interfaces interited are  product component
    def serialize(self, song, format): #this is the application code. i.e. client component
        serializer = self._get_serializer(format)
        return serializer(song)
    def _get_serializer(self, format):# this is the                        creator component
        serializers = {
            'JSON': self._serialize_to_jons,
            'XML': self._serialize_to_xml
        }
        serializer = serializers.get(format)
        if not serializer:
            raise ValueError(format)
        return serializer
    
#step 3: When methods are not using 'self', seperate them into functions.
class SongSerializer(object):
    #of course this class is not necessary. But imagine it's an existed code.
    #Refactor this class into a function will break the interface in other scirpt.
    #unless we have high % of unit test coverage, otherwise be carefule to do so.
    def serialize(self, song, format):
        serializer = _get_serializer(format)
        return serializer(song)

# def serialize(song, format):
#     serializer = _get_serializer(format)
#     return serializer(song)

def _get_serializer(format):
    serializers = {
        'JSON': _serialize_to_jons,
        'XML': _serialize_to_xml
    }
    serializer = serializers.get(format)
    if not serializer:
        raise ValueError(format)
    return serializer

def _serialize_to_jons(song):
    payload = {
            'id': song.song_id,
            'title': song.title,
            'artist': song.artist
        }
    return json.dumps(payload)
def _serialize_to_xml(song):
    song_info = et.Element('song', attrib={'id': song.song_id})
    title = et.SubElement(song_info, 'title')
    title.text = song.title
    artist = et.SubElement(song_info, 'artist')
    artist.text = song.artist
    return et.tostring(song_info, encoding='unicode')
def client(target, target_id):
    product = _creator_get_implementation(target_id)
    return product(target)
def _creator_get_implementation(target_id):
    products = {
        'target_id1': product1,
        'target_id2': product2
    }
    product = products.get(target_id)
    if not product:
        raise ValueError(target_id)
def product1(target):
    return
def product2(target):
    return

Prototype

replaced by the copy module

Builder

Still have to be implemented by syntax

7 structural patterns

Still exists and useful to organize the code.

Adapter

Still useful. Override methods with unified methods names

Bridge

Still useful. Seperate a business layer out of the lower level so that lower level only stores data, business layer operates

Composite

Still useful. Has a child object which is the basic element or the object like itself recursively. Store children objects as list, dict or just the object itself.

Facade

Still useful. Builder creates a complex object. Facade operates a complex object. Simple example is find()method of a object that lets us do not have to write iteration to search the whole data.

Flyweight

still useful

Proxy

Performed by __getattr__() in Python

Decorator

same as decorators in python

11 behavioral patterns

They are big solutions for big probelms and some of the patterns turn up into libraries. But still useful in python. They can be collapsed into callbacks or data structures that hold functions.

Interperter

Python itself is interpreted

Iterator

is built-in in Python. implement yield in __iter__ method

Mediator

still useful

Memento

Observer

still useful. esp in GUI and DOMs

State