常见的设计模式

创建者模式

工厂模式

工厂模式属于创建者模式

工厂模式:目的时提供方便快捷的对象创建方式,当对象的创建类型多样时,可以使用工厂方法;

当对象与对象关系较复杂时,且存在关联时,可构建抽象工厂方法;

工厂模式属于创建型模式,目的是提供更好的创建对象的方式

工厂模式通常有两种:

  • 工厂方法(Factory Method)—— 它是一个方法,对于不同的参数返回不同的对象
  • 抽象工厂,它是一组用于创建一系列相关事物对象的工厂方法,例如一辆车的各个部件

两种模式都可以用于以下场景:

  • 想要追踪对象的创建时
  • 想要将对象的创建与使用解耦时
  • 想要优化性能和资源占用时

工厂方法

Django 框架中使用工厂方法模式来创建表单字段。Django 的 forms 模块支持不同类型的字段(CharField、EmailField)

https://github.com/django/django/blob/main/django/forms/fields.py

创建多个工厂方法也完全没有问题,实践中通常也这么做,对相似的对象创建进行逻辑分组,每个工厂负责一个组。

工厂犯法可以在必要时创建新的对象,从而提高性能和内存使用率。若直接实例化类来创建对象,那么需要分配额外的内存

例如:XML、Atom、YAML、JSON 解析器

DEMO

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
import xml.etree.ElementTree as etree
import json


class JsonParser(object):
def __init__(self, filepath: str):
self._data = {}
with open(filepath, mode="r", encoding="utf-8") as f:
self._data = json.load(f)

@property
def data(self):
return self._data


class XMLParser(object):
def __init__(self, filepath: str):
self._tree = etree.parse(filepath)

@property
def data(self):
return self._tree


def parser_factory(filepath: str):
if filepath.endswith('json'):
parser = JsonParser
elif filepath.endswith('xml'):
parser = XMLParser
else:
raise ValueError(f"cannot found the parser of {filepath}")

return parser(filepath)


def main():
json_factory = parser_factory("./test.json")
j_data = json_factory.data
print(j_data)


if __name__ == '__main__':
main()

抽象工厂

一个抽象工厂是(逻辑上的)一组工厂方法,其中的每个工厂方法负责产生不同种类的对象

抽象工厂模式是工厂方法的一种泛化,所以它能提供相同的好处。

我们怎么知道何时该使用工厂方法,何时又该使用抽象工厂?答案是,通常一开始时使用工厂方法,因为它更简单。如果后续发现应用需要许多工厂方法,那么将创建一系列对象的过程合并在一起更合理,从而最终引入抽象工厂。

DEMO

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
# 类别:男巫
class Wizard(object):
def __init__(self, name):
self.name = name

def __str__(self):
return self.name

def interact_with(self, obstacle):
print('{} the Wizard battles against {} and {}!'.format(self, obstacle, obstacle.action()))

# 类别二、兽人
class Ork:
def __str__(self):
return 'an evil ork'
def action(self):
return 'kills it'

# 类别二、工厂方法
class WizardWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Wizard World ———'
def make_character(self):
return Wizard(self.player_name)
def make_obstacle(self):
return Ork()

# 抽象工厂方法
class GameEnvironment:
def __init__(self, factory):
self.hero = factory.make_character()
self.obstacle = factory.make_obstacle()
def play(self):
self.hero.interact_with(self.obstacle)

def main():
# 一堆参数设置
environment = GameEnvironment(game(name))
environment.play()

if __name__ == '__main__':
main()

结构型模式

装饰器模式

装饰器模式允许我们将一个提供核心功能的对象和其他可以改变这个功能的对象”包裹”在一起。

装饰器模式主要有两种用途:

  • 增强一个组件向另一个组件发送数据时的响应能力
  • 支持多种可选行为

闭包方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__} running...")
return func(*args, **kwargs)

return wrapper


@log_call
def run_example():
print("running...")


if __name__ == '__main__':
run_example()

对象方式

__call__ 方法是实现对象方式的关键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools
class Decorator(object):
def __int__(self, params):
self.params = params

def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# do something
func(*args, **kwargs)
return wrapper


def hello():
print("hello")


hello()

适配器模式

主要用处理一个系统中不同实体(比如类和对象)之间的关系,关注的是提供一种简单的对象组合方式来创造新的功能

适配器模式(Adapter pattern)的主要作用就是把原本不兼容的接口,通过适配修改做到统一。

如果我们希望把一个老组件用于一个新系统中,或者反过来,在一个新系统中兼容老的组件,这时就需要编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行所有修改。

例如:每个类都实现自己语言,使用适配器进行适配,使得每个实例发出声音

DEMO

__dict__ && __getattr__是实现适配器的关键

__getattr__()是仅当属性不能在实例的__dict__或它的类(类的__dict__),或父类的__dict__中找到时,才被调用。一般在代码中包含一个对getattr()內建函数的调用

每一个类都会用一个字典,把它包含的属性放到自己的字典里。

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
56
57
58
59
60
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%s" % ("!" * octane_level)


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

def __getattr__(self, attr):
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("A", obj.name, "goes", obj.make_noise())


if __name__ == '__main__':
main()

外观模式

外观模式在很多方面与适配器模式相同。主要的区别在于,外观模式试图从一个复杂的系统中抽象出一个简单的接口;而适配器只不过是将一个现有的接口匹配到另一个。

外观模式的目的是为拥有多个组件的复杂系统提供简单的接口。这个系统内的对象为了完成复杂的任务和交互需求会进行各种直接的交互。该系统通常存在某些典型的用途

这里用一个自己泡茶去茶馆喝茶的例子,自己泡茶的话,需要自行准备茶叶、茶具和开水,而自己去茶馆喝茶,最简单的方式就是和茶馆服务员说想要一杯什么茶,是铁观音、碧螺春还是西湖龙井。

这里典型的茶的种类就是典型

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright DataGrand Tech Inc. All Rights Reserved.
Author: tianzhichao
File: facade.py
Time: 2023/2/20 10:27 PM
"""


class Water(object):
def get_water(self):
print("hot water")


class TeaPot(object):
def get_teapot(self):
print("get teapot")


class Tea(object):
def red_tea(self):
print("red tea")


class Facade(object):
def __init__(self):
self.water = Water()
self.teapot = TeaPot()
self.tea = Tea()

def get_red_tea(self):
self.water.get_water()
self.teapot.get_teapot()
self.tea.red_tea()


def tea_client():
facade = Facade()
facade.get_red_tea()


if __name__ == '__main__':
tea_client()

观察者模式

观察者模式在撞见检测和事件处理等场景中时非常有用的。

这种模式确保一个 核心 对象可以由一组未知并可能正在扩展的 观察者 对象来监控。

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
class Inventory(object):
def __init__(self):
self._product = None
self._quality = 0
self.observers = []

def attach(self, observer):
self.observers.append(observer)

@property
def product(self):
return self._product

@product.setter
def product(self, value):
self._product = value
self._update_observer()

def _update_observer(self):
for observer in self.observers:
observer()


class ConsoleObserver(object):
def __init__(self, inventory):
self._inventory = inventory

def __call__(self, *args, **kwargs):
print(f"the name of product: {self._inventory.product}")


if __name__ == '__main__':
i = Inventory()
c = ConsoleObserver(i)
i.attach(c)

i.product = "test"

享元模式

享元模式是一种内存优化模式。由于对象创建的开销,面向对象的系统可能会面临性能问题。

当我们创建一个新对象时,需要分配额外内存

享元设计模式通过为相似对象引入数据共享来最小化内存使用,提升性能。

一个享元(Flyweight)就是一个包含状态独立的不可变(又称固有的)数据的共享对象。依赖状态的可变(又称非固有的)数据不应是享元的一部分,因为每个对象的这种信息都不同,无法共享。若享元需要非固有的数据,应该由客户端代码显式地提供。

享元模式使用的几个条件

  • 应用需要大量的对象
  • 对象太多,存储/渲染它们的代价太大。一旦移除对象中的可变状态(因为在需要之时,应该由客户端代码显式地传递给享元),多组不同的对象可被相对更少的共享对象所替代。
  • 对象 ID 对于应用不重要。对象共享会造成 ID 比较的失败,所以不能依赖对象 ID

DEMO

例如在一个游戏中,需要很多树对象,不变的是树对象的样子,变化的地方是树的年纪和坐标。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import random
from enum import Enum

TreeType = Enum('TreeType', 'apple_tree cherry_tree peach_tree')

# tree
class Tree(object):
tree_pool = {}
def __new__(cls, tree_type):
# 判断树的类型
obj = cls.tree_pool.get(tree_type, None)
if not obj:
obj = object.__new__(cls)
cls.tree_pool[tree_type] = obj
obj.tree_type = tree_type

return obj

# 位置坐标
def render(self, age, x, y):
print(f'render a tree of type {self.tree_type} and age {age} at ({x}, {y})')


def main():
# 以下仅分配了 3 课树的内存
age_min, age_max = 1, 30
min_point, max_point = 0, 100
tree_counter = 0

for _ in range(10):
t1 = Tree(TreeType.apple_tree)
t1.render(random.randint(age_min, age_max),
random.randint(min_point, max_point),
random.randint(min_point, max_point)
)

tree_counter += 1

for _ in range(5):
t2 = Tree(TreeType.cherry_tree)
t2.render(random.randint(age_min, age_max),
random.randint(min_point, max_point),
random.randint(min_point, max_point)
)

tree_counter += 1

for _ in range(3):
t3 = Tree(TreeType.peach_tree)
t3.render(random.randint(age_min, age_max),
random.randint(min_point, max_point),
random.randint(min_point, max_point)
)

tree_counter += 1

print('trees rendered: {}'.format(tree_counter))
print('trees actually created: {}'.format(len(Tree.tree_pool)))

t4 = Tree(TreeType.cherry_tree)
t5 = Tree(TreeType.cherry_tree)
t6 = Tree(TreeType.apple_tree)
t7 = Tree(TreeType.peach_tree)

print(Tree.tree_pool)
print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5))) # True
print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6))) # False


if __name__ == '__main__':
main()

命令模式

命令设计模式帮助我们将一个操作(撤销、重做、复制、粘贴)封装成一个对象。

这意味着创建一个类,包含实现该操作所需要的所有逻辑和方法。这样做的优势如下:

  • 我们并不需要直接执行一个命令。命令可以按照希望执行
  • 调用命令的对象与指导如何执行命令的对象解耦。调用者无需知道命令的实现细节。
  • 如果有意义,可以将多个命令组织起来,这样调用者能够按顺序执行它们,例如,在实现一个多层撤销命令时,这是很有用的。

应用案例

  • GUI 按钮和菜单项:PyQt 例子使用命令模式来实现按钮和菜单项上的的动作
  • 其他操作:除了撤销,命令模式可以实现任何操作。其中一些例子包括剪切、复制、粘贴、重做,文本大写
  • 事务性行为和日志记录:事务型行为和日志记录对于为变更记录一份持久化日志是很重要的。操作系统用他来从系统奔溃中恢复,关系型数据库用它来实现事务,文件系统用它来实现 snapshot,而安装程序(向导程序)用它来恢复取消的安装
  • 宏:在这里,宏是指一个动作序列,可在任意时间点按要求进行录制和执行。流行的编辑器(比如 Emacs 和 Vim)都支持宏。

实现

实现最基本的文件操作工具

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright DataGrand Tech Inc. All Rights Reserved.
Author: tianzhichao
File: command.py
Time: 2023/2/21 11:20 PM
"""
import os
from abc import ABCMeta, abstractmethod
verbose = True


# base class
class Command(metaclass=ABCMeta):
@abstractmethod
def execute(self):
pass

@abstractmethod
def undo(self):
pass


class RenameFile(Command):
"""
重命名文件,包含撤销操作
"""
def __int__(self):
pass

def execute(self):
pass

def undo(self):
pass


class CreateFile(Command):
"""
创建文件,包含撤销操作
"""
def __init__(self, path, txt="hello world\n"):
self.path = path
self.txt = txt

def execute(self):
pass

def undo(self):
pass


class ReadFile(Command):
def __int__(self, path):
self.path = path

def execute(self):
pass

def undo(self):
pass


class DeleteFile(Command):
def __int__(self, path):
self.path = path

def execute(self):
pass

def undo(self):
pass

refs

领域驱动设计:https://book.douban.com/subject/26819666/

软件设计:https://l1nwatch.gitbook.io/python-design-mode/

-------------THANKS FOR READING-------------