Three common basic design patterns
-
Creation mode
Provide instantiation methods and corresponding creation methods for suitable situations.
-
Structured model
Used to deal with the relationship between entities, so that these entities can work together better.
-
Behavior mode
It is used for communication between different entities and provides an easier and more flexible communication method for communication between entities.
Detailed mode introduction
Creation type
1. Factory Method
-
effect
Define an interface for creating objects and let subclasses decide which class should be instantiated. Factory Method delays the instantiation of a class to its subclasses.
-
Applicable scene
- When a class does not know the class of the object it must create.
- When a class wants its subclasses to specify the objects it creates.
-
Sample Demo
class ChinaGetter(object): def __init__(self): self.trans = dict(dog='小狗',cat='小猫') def get(self, msg_id): try: return self.trans[msg_id] except KeyError: return str(msg_id) class EnglishGetter(object): def get(self, msg_id): return str(msg_id) def get_localizer(language='English'): languages = dict(English=EnglishGetter, China=ChinaGetter) return languages[language]() # create localizer e, g = get_localizer('English'), get_localizer('China') for text in "A cat sits on the desk".split(): print(e.get(text), g.get(text))
2. Abstract Factory
-
effect
Provide an interface for creating related or interdependent objects without specifying their specific classes.
-
Applicable scene
- When a system is independent from the creation, combination and presentation of its products
- When a system requires one of multiple product families to configure
- When emphasizing the design of a series of related product objects for joint use
- When providing a product class library, but only want to display their interfaces instead of implementation.
-
Sample Demo
import random class Cat(object): def speak(self): return 'meow' def __str__(self): return 'Cat' class Dog(object): def speak(self): return 'woolf' def __str__(self): return 'Dog' class CatFactory(object): def get_pet(self): return Cat() def get_food(self): return 'Cat food' class DogFactory(object): def get_pet(self): return Dog() def get_food(self): return 'Dog food' class PetShop(object): def __init__(self, animal_factory=None): self.factory = animal_factory def show_pet(self): pet = self.factory.get_pet() print('This is a lovely ', str(pet)) print('pet speaks ', pet.speak()) print('pet eats ', pet.get_food()) def get_factory(): return random.choice([DogFactory, CatFactory])
3. Builder
-
effect
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
-
Applicable scene
- When the creation of a complex object is independent of the sub-components that make up the object, and how the sub-components are assembled
- Allow different objects to be created by different construction processes
-
Sample Demo
class Director(object): def __init__(self): self.builder = None def construct_building(self): self.builder.new_building() self.builder.build_floor() self.builder.build_size() def get_building(self): return self.builder.new_building class Builder(object): def __init__(self): self.building = None def new_building(self): self.building = Building() class BuilderHouse(Builder): def build_floor(self): self.building.floor = "One" def build_size(self): self.building.size = "Big" class BuilderFlat(Builder): def build_floor(self): self.building.floor = "Two" def build_size(self): self.building.size = "Small" class Building(object): def __init__(self): self.floor = None self.size = None def __repr__(self): return "Floor : %s | Size: %s" % (self.floor, self.size) if __name__ == '__main__': director = Director() director.builder = BuilderHouse() director.construct_building() building = director.get_building() print(building) director.builder = BuilderFlat() director.construct_building() building = director.get_building() print(building)
4. Prototype (prototype)
-
effect
Use prototype instances to create objects, and copy these prototypes to create new objects.
-
Applicable scene
- Dynamic loading, that is, when the class to be instantiated is specified at runtime
- To avoid creating a factory class level parallel to the product class level
- When an instance of a class can only have a combination of several different states
It is recommended that a corresponding number of prototypes, and clone them, be more convenient than instantiating manually with the appropriate state each time.
-
Sample Demo
import copy class Prototype: def __init__(self): self._objects = {} def register_object(self, name, obj): self._objects[name] = obj def unregister_object(self, name): del self._objects[name] def clone(self, name, **attr): obj = copy.deepcopy(self._objects.get(name)) obj.__dict__.update(attr) return obj def main(): class A: def __str__(self): return "I"'m A.' a = A() prototype = Prototype() prototype.register_object('a', a) b = prototype.clone('a', a=1, b=2, c=3) print(a) print(b.a, b.b, b.c) if __name__ == '__main__': main()
5. Singleton (single case)
-
effect
Ensure that only one instance of a class exists
-
Applicable scene
- When the class allows only one instance, and the client can access it from a global variable
- When this unique instance can be extended by subclasses, and the client can use an extended instance without changing the code
-
Sample Demo
class Singleton(object): def __new__(cls, *args, **kwargs): if not hasattr(cls, '_instance'): org = super(Singleton, cls) cls._instance = org.__new__(cls, *args, **kwargs) return cls._instance class SingleSpam(Singleton): def __init__(self, s): self.s = s def __str__(self): return self.s if __name__ == '__main__': spam = SingleSpam('spam') print(id(spam), spam) spa = SingleSpam('spa') print(id(spa), spa) print(id(spam), spam)
Structural type
6. Adapter Class / Object (Adapter)
-
effect
Convert the interface of one class to another interface that the client wants. The Adapter mode enables classes that could not work together because of incompatible interfaces to work together again.
-
Applicable scene
- Trying to use an existing class whose interface does not meet your needs
- Trying to create a reusable class that can work with other unrelated or unpredictable classes
- Trying to use existing subclasses, but cannot subclass each one to match their interface.
-
Sample Demo
import os class Dog(object): def __init__(self): self.name = "Dog" def bark(self): return "woof" class Cat(object): def __init__(self): self.name = "Cat" def meow(self): return "meow" class Human(object): def __init__(self): self.name = "Human" def speak(self): return "hello" class Car(object): def __init__(self): self.name = "Car" def make_noise(self, octane_level): return "vroom" % ("!" * octane_level) class Adapter(object): def __init__(self, obj, adapted_methods): self.obj = obj self.__dict__.update(adapted_methods) def __getattr__(self, item): return getattr(self.obj, attr) def main(): objects = [] dog = Dog() objects.append(Adapter(dog, dict(make_noise=dog.bark))) cat = Cat() objects.append(Adapter(cat, dict(make_noise=cat.meow))) human = Human() objects.append(Adapter(human, dict(make_noise=human.speak))) car = Car() car_noise = lambda: car.make_noise(3) objects.append(Adapter(car, dict(make_noise=car_noise))) for obj in objects: print("{0} said {1}".format(obj.name, obj.make_noise())) if __name__ == "__main__": main()
7. Bridge
-
effect
Separate the abstract part and its realization part, so that they can change independently
-
Applicable scene
- Try to decouple the abstraction and the realization part.
- The abstraction of the class and its implementation can be expanded by generating subclasses.
- Modifying an abstract implementation part should not affect the client, that is, the client does not need to be recompiled.
-
Sample Demo
# ConcreteImplementor class DrawingApi1(object): def draw_circle(self, x, y, radius): print("Api_1.circle at {}:{} radius {}".format(x, y, radius)) # ConcreteImplementor class DrawingApi2(object): def draw_circle(self, x, y, radius): print("Api_1.circle at {}:{} radius {}".format(x, y, radius)) # Abstraction class CircleShape(object): def __init__(self, x, y, radius, drawing_api): self._x = x self._y = y self._radius = radius self._drawing_api = drawing_api # low level, i.e. implementation def draw(self): self._drawing_api.draw_circle(self._x, self._y, self._radius) # high level i.e. Abstraction def scale(self, pct): self._radius *= pct def main(): shapes = ( CircleShape(1, 2, 3, DrawingApi1()), CircleShape(5, 7, 11, DrawingApi2()) ) for shape in shapes: shape.scale(2.5) shape.draw() if __name__ == '__main__': main()
8. Composite
-
effect
Combining objects into a tree structure to represent a part-whole hierarchy makes client use of single objects and combined objects more consistent.
-
Applicable scene
- Need for part-whole hierarchy
- I hope that the client ignores the difference between the combined object and the single object and uses the combined structure uniformly.
-
Sample Demo
# Abstraction class Component(object): def __init__(self, name): self._name = name def add(self, com): pass def display(self, depth): pass # Instance class Leaf(Component): def add(self, com): print("leaf can not add") def display(self, depth): name = "-" * depth name += self._name print(name) class Composite(Component): def __init__(self, name): super(Composite).__init__(name) self.entities = [] def add(self, entity): self.entities.append(entity) def display(self, depth): name = "-" * depth name += self._name for entity in self.entities: entity.display(depth+2) if __name__ == "__main__": p = Composite("Wong") p.add(Leaf("Lee")) p.add(Leaf("Zhao")) p1 = Composite("Wu") p1.add(Leaf("San")) p.add(p1) p.display(1)
9. Decorator (decorator)
-
effect
Add some extra features to an object dynamically. In terms of usage, the decorator pattern is more flexible than the way of generating subclasses.
-
Applicable scene
- Add responsibilities to individual objects in a dynamic and transparent manner without affecting other objects
- Handling revocable duties
- When the method of generating subclasses cannot be used for expansion:
- There may be a large number of independent extensions. To support each combination, a large number of subclasses will be generated, and the number of subclasses will explode.
- Class definitions may be hidden, or class definitions cannot be used to generate subclasses
-
Sample Demo
class Foo(object): def func1(self): print("original func1") def func2(self): print("original func2") class FooDecorator(object): def __init__(self, decorator): self._decorator = decorator def func1(self): print("decorated func1") self._decorator.func1() def __getattr__(self, item): return getattr(self._decorator, item) if __name__ == '__main__': foo = Foo() foo_decorator = FooDecorator(foo) foo_decorator.func1() foo_decorator.func2()
10. Facade (appearance)
-
effect
To provide a consistent interface for a set of interfaces of the subsystem, the Facade mode defines a high-level interface, making the subsystem easier to use.
-
Applicable scene
- When the subsystem becomes complicated due to continuous evolution, trying to provide a simple interface for the complex subsystem
- When most patterns are used, more and smaller classes will be generated, trying to reuse subclasses or customize the subsystem
- There is a great dependence between the client and the implementation of the abstract class. The introduction of Facade can separate the subsystem from the client and other subsystems, improving the independence and portability of the subsystem
- When you need to build a hierarchical subsystem, use the Facade pattern to define the entry point of each layer of the subsystem
- If the subsystems depend on each other, Facade can be used to communicate, simplifying the dependence between them
-
Sample Demo
import time SLEEP = 0.5 class TC1(object): def run(self): print("###### In Test 1 ######") time.sleep(SLEEP) print("Setting up") time.sleep(SLEEP) print("Running test") time.sleep(SLEEP) print("Tearing down") time.sleep(SLEEP) print("Test 1 Finished") class TC2(object): def run(self): print("###### In Test 2 ######") time.sleep(SLEEP) print("Setting up") time.sleep(SLEEP) print("Running test") time.sleep(SLEEP) print("Tearing down") time.sleep(SLEEP) print("Test 2 Finished") class TC3(object): def run(self): print("###### In Test 3 ######") time.sleep(SLEEP) print("Setting up") time.sleep(SLEEP) print("Running test") time.sleep(SLEEP) print("Tearing down") time.sleep(SLEEP) print("Test 3 Finished") # Facade class TestRunner(object): def __init__(self): self.tc1 = TC1() self.tc2 = TC2() self.tc3 = TC3() self.tests = [i for i in (self.tc1, self.tc2, self.tc3)] def run_all(self): for test in self.tests: test.run() # Client if __name__ == '__main__': test_runner = TestRunner() test_runner.run_all()
11. Flyweight (Flyweight)
-
effect
With the help of sharing technology, effectively support a large number of fine-grained objects
-
Applicable scene
- An application uses a lot of objects, causing a lot of storage overhead
- Most of the state of an object can be changed to an external state. If you delete the external state of an object, you can replace many groups
- The application does not depend on object identification. Since Flyweight objects can be shared, the identification test will return true values for objects that are conceptually distinct.
-
Sample Demo
import weakref class Card(object): _CardPool = weakref.WeakValueDictionary() def __new__(cls, value, suit): obj = Card._CardPool.get(value + suit, None) if not obj: obj = object.__new__(cls) Card._CardPool[value + suit] = obj obj.value, obj.suit = value, suit return obj def __repr__(self): return "<Card: %s%s>" % (self.value, self.suit) # Client if __name__ == '__main__': c1 = Card("9", "h") c2 = Card("9", "h") print(c1, c2) print(c1 == c2) print(id(c1), id(c2))
12. Proxy
-
effect
Provide a proxy for other objects to control access to such objects
-
Applicable scene
- When you need to use more general and complex object pointers instead of simple pointers, use the Proxy mode.
- Remote proxy: Provides a local representative for an object in different address spaces
- Virtual agent: create expensive objects as needed
- Protection agent: Control access to the original object, for the object should have different access rights
- Smart proxy: Instead of simple pointers, perform some additional operations when accessing objects. Typical use: counting references to actual objects.
- When a persistent object is referenced for the first time, load it into memory
- Before accessing an actual object, check whether a lock is already locked to ensure that other objects cannot change it
- When you need to use more general and complex object pointers instead of simple pointers, use the Proxy mode.
-
Sample Demo
import time class SaleManager(object): def work(self): print("Sale Manager is working...") def talk(self): print("sale Manager ready to talk.") class Proxy(object): def __init__(self): self.busy = False self.sales = None def work(self): print("Proxy checking for Sale Manager availability.") if not self.busy: self.sales = SaleManager() time.sleep(2) self.sales.talk() else: time.sleep(2) print("Sale Manager is busy.") # Client if __name__ == '__main__': proxy = Proxy() proxy.work() proxy.busy = True proxy.work()
13. Interpreter
-
effect
Given a language, define an expression of its grammar (Expression), and define an interpreter, the interpreter uses this expression (Expression) to interpret sentences in the interpreted language.
-
Applicable scene
- When a language needs interpretation and execution, and you can express the sentence in this language as an AST, you can use the interpreter mode (to be refined in detail)
-
Sample Demo
class Context(object): def __init__(self): self.input = "" self.output = "" class AbstractExpression(object): def interpret(self, context): pass class Expresssion(AbstractExpression): def interpret(self, context): print("terminal interpret") class NonterminalExpression(AbstractExpression): def interpret(self, context): print("Nonterminal interpret") # Client if __name__ == '__main__': context = "" c = [] c.append(Expresssion()) c.append(NonterminalExpression()) c.append(Expresssion()) c.append(Expresssion()) for item in c: c.interpret(context)
14. Template Method
-
effect
Define the skeleton of the algorithm in an operation, and delay some steps into subclasses. TemplateMethod allows subclasses to redefine certain steps of an algorithm without changing the structure of an algorithm.
-
Applicable scene
- Implement the unchanging part of an algorithm at once, and leave the variable behavior to subclasses to implement.
- The common behavior in each subclass should be extracted and concentrated into a common parent class to avoid code duplication. This is a good example of "refactoring to generalize" as described by Opdyke and Johnson
- Control subclass extension
-
Sample Demo
# Skeletons
string = "apple bear cat"
def iter_elements(getter, action):
for elem in getter():
action(elem)
def rev_elements(getter, action):
for elem in getter()[::-1]:
action(elem)
# Getters
def get_list():
return string.split()
def get_lists():
return [list(x) for x in string.split()]
# Actions
def print_item(item):
print(item)
def print_rev_item(item):
print(item[::-1])
def make_templates(skeleton, getter, action):
def template():
skeleton(getter, action)
return template
# Client
if __name__ == '__main__':
templates = [make_templates(s, g, a)
for g in (get_list, get_lists)
for a in (print_item, print_rev_item)
for s in (iter_elements, rev_elements)
]
for template in templates:
template()