Scott's Blog

学则不固, 知则不惑

0%

Python 设计模式-享元与命令模式

这篇文章讨论了享元模式与命令模式。

这是设计模式系列文章的一部分,点击查看该系列的其他文章。

享元模式

元、气之始,引申为元气。享元模式即共享最初的那部分,哪部分呢?

假设你有一个需求需要创建大量的类实例,利用享元模式,就可以保证共享同一状态的对象,可以同时使用该共享状态的内存。

举个例子,之前聊过的汽车销售系统。对于每一辆车,我们可以加装不同的配置,比如有尊享版、豪华版等等。

不同版本之间,其实是大同小异。

如果对于每辆车我们都去统计它有什么功能,没有什么功能,则会产生巨大的浪费。

我们可以通过共享对象去存储那些与型号相关的特性列表。

享元在 Python 中的实现类似单例模式。

但单例模式返回的是一个类的实例,而享元模式则是根据指定的不同参数,返回对应的实例。

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
import gc  # 垃圾回收器
import weakref # 弱引用数据结构包

class CarModel:
"""弱引用的字典,内部的对象弱没有被引用,则会被回收。
"""
_models = weakref.WeakValueDictionary()

def __new__(cls, model_name, *avgs, **kvargs):
"""这里对新实例的创建自定义,与单例模式有点类似
"""
model = cls._models.get(model_name)
if not model:
model = super().__new__(cls)
cls._models[model_name] = model

return model


def __init__(self, model_name, air=False, title=False,
cruise_control=False, power_locks=False,
allow_whells=False, use_charger=False):
"""实例的初始化,在这里完成
"""
self.model_name = model_name
self.air = air
self.title = title
self.cruise_control = cruise_control
self.power_locks = power_locks
self.allow_whells = allow_whells
self.use_charger = use_charger

def check_serial(self, serial_number):
print(
"Sorry We are anble to check",
"the seria number {0} on the {1}",
"at this time".format(self.number, self.model_name)
)


class Car:
def __init__(self, model, color, serial):
self.model = model
self.color = color
self.serial = serial

def check_serial(self):
return self.model.check_serial(self.serial)

上面定义了两个类,一个是汽车模版,一个是汽车。我们来生产几辆车:

1
2
3
4
5
6
7
8
dx = CarModel("FIT DX")
lx = CarModel("FIT LX", air=True, cruise_control=True, power_locks=True, title=True)


# 生产一些车
car1 = Car(model=dx, color='blue', serial='DX001')
car2 = Car(model=dx, color='black', serial='DX002')
car3 = Car(model=lx, color="red", serial="LX003")

现在来观察他们的内存地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# display(id(lx)) -> 4402294352

# 删除 lx 和对它的引用,让垃圾回收器回收
del lx
del car3
gc.collect()

# 重新创建一个新的 lx,其地址已经变了,因为生成了新的 lx
lx = CarModel("FIT LX", air=True, cruise_control=True, power_locks=True, title=True)
# display(id(lx)) -> 4401731376

# 再创建一个其它版本的 lx,没有 air 和其他的选项,发现返回的还是第二遍创建的
lx = CarModel("FIT LX")
# display(id(lx)) -> 4401731376

享元模式使用起来比普通的类实现更负责,但如果你有成百上千的类实例需要创建的时候,享元模式可以极大的节省你的内存,可以说享元模式是专为节省内存而设计的。

命令模式

命令模式在必须被完成的行为调用这些动作的对象之间添加了一个抽象层,这句话可能比较难理解,我们看一个例子。

这个模式在图形窗口中的操作中应用的比较多。

我们实现一个窗口程序,它有退出和保存的功能。

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
import sys


class Window:
def exit(self):
sys.exit()


class Document:
def __init__(self, file_name):
self.file_name = file_name
self.content = "This file can not be modified"


def save(self):
with open(self.file_name, 'w') as file:
file.write(self.content)


class ToolBarButton:
def __init__(self, name, iconname):
self.name = name
self.iconname = iconname


def click(self):
self.command.execute()


class MenuItem:
def __init__(self, menu_name, manu_itemname):
self.manu_name = menu_name
self.menu_itemname = manu_itemname


def click(self):
self.command.execute()


class KeyboardShortCut:
def __init__(self, key, modifier):
self.key = key
self.modifier = modifier


def keypress(self):
self.command.execute()


class SaveCommand:
def __init__(self, document):
self.document = document


def execute(self):
self.document.save()


class ExitCommand:
def __init__(self, document):
self.document = document


def execute(self):
self.document.save()

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 新建一个窗口
window = Window()
# 新建一个文档
document = Document("A Great Document!")

# 新建两个指令,需要传入文档
save_command = SaveCommand(document)
exit_command = ExitCommand(document)

# 绑定操作
# 举例:当 ToolBarButton 内部的 click 被调用
# click -> save_command.execute(内部引用了document) -> document.save
save_button = ToolBarButton('save', 'save.png')
save_button.command = save_command

save_keystroke = KeyboardShortCut('s', 'ctrl')
save_keystroke.command = save_command

exit_menu = MenuItem('File', 'Exit')
exit_menu.command = exit_command

还有一种更简洁、更 Python 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sys


class Window:
def exit(self):
sys.exit()

class MenuItem:
def click(self):
self.command()

window = Window()
menu_item = MenuItem()
menu_item.command = window.exit

或者直接调用类,只需实现 __call__ 方法即可:

1
2
3
4
5
6
7
8
class SaveCommand:
def __init__(self, document):
self.document = document


def __call__(self):
"""实现了 call 方法的类可以直接被调用"""
self.document.save()