python 设计模式 03

  |  

行为型模式

解释器模式

内容

给定一个语言,定义它的文法、语法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

主要是解释器使用,这里省略

责任链模式

内容

责任链模式,将多个处理方法连接成一条链条,请求将在这条链条上流动直到该链条中有一个节点可以处理该请求。通常这条链条是一个对象包含对另一个对象的引用而形成链条,每个节点有对请求的条件,当不满足条件将传递给下一个节点处理。

责任链模式就是种“推卸”责任的模式,你的问题在我这里能解决我就解决,不行就把你推给另一个对象。至于到底谁解决了这个问题了呢?我管呢!

角色:

  • 抽象处理者(Handler):定义了处理请求的抽象方法和一个设置责任链的下一个处理者的方法。
  • 具体处理者(ConcreteHandler):实现处理请求的方法,判断自己能否处理本次请求,如果能,则处理,如果不能,则把请求转发给责任链的下一个处理者。
  • 客户类(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
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from abc import ABC, abstractmethod
from enum import Enum


class Request(Enum):
VACATION = 1
SALARY_RAISE = 2


class Handler(ABC):
@abstractmethod
def set_next(self, handler):
pass

@abstractmethod
def handle(self, request: Request, number: int):
pass


class ConcreteHandler(Handler):
def __init__(self):
self._next = None

def set_next(self, handler):
self._next = handler
return handler

def handle(self, request, number):
if self._next:
return self._next.handle(request, number)
print("No handler found for request: {}".format(request))

if request is Request.VACATION:
print("You can't take a vacation.")
elif request is Request.SALARY_RAISE:
print("You can't get a raise.")
return None


class GeneralManager(ConcreteHandler):
def handle(self, request, number):
if request is Request.VACATION:
if number <= 10:
print("General Manager: I'll approve your vacation.")
return
elif request is Request.SALARY_RAISE:
if number <= 1000:
print("General Manager: I'll approve your salary raise.")
return
super().handle(request, number)


class DepartmentManager(ConcreteHandler):
def handle(self, request, number):
if request is Request.VACATION:
if number <= 5:
print("Department Manager: I'll approve your vacation.")
return
elif request is Request.SALARY_RAISE:
if number <= 500:
print("Department Manager: I'll approve your salary raise.")
return
print("Department Manager: My permissions are insufficient. I'll pass this request to next one.")
super().handle(request, number)


class Supervisor(ConcreteHandler):
def handle(self, request, number):
if request is Request.VACATION:
if number <= 3:
print("Supervisor: I'll approve your vacation.")
return
elif request is Request.SALARY_RAISE:
if number <= 300:
print("Supervisor: I'll approve your salary raise.")
return
print("Supervisor: My permissions are insufficient. I'll pass this request to next one.")
super().handle(request, number)


class Employee(ConcreteHandler):
def handle(self, request, number):
if request is Request.VACATION:
if number <= 1:
print("Employee: I'll approve your vacation.")
return
elif request is Request.SALARY_RAISE:
if number <= 100:
print("Employee: I'll approve your salary raise.")
return
print("Employee: My permissions are insufficient. I'll pass this request to next one.")
super().handle(request, number)


class Client:
def __init__(self):
self._employee = Employee()
self._supervisor = Supervisor()
self._department_manager = DepartmentManager()
self._general_manager = GeneralManager()

self._employee.set_next(self._supervisor).set_next(self._department_manager).set_next(self._general_manager)

def make_request(self, request, number):
self._employee.handle(request, number)


if __name__ == "__main__":
client = Client()
print("######## Client: I want to take a vacation for 2 days.")
client.make_request(Request.VACATION, 2)
print("######## Client: I want to raise my salary for 200.")
client.make_request(Request.SALARY_RAISE, 200)
print("######## Client: I want to take a vacation for 7 days.")
client.make_request(Request.VACATION, 7)
print("######## Client: I want to raise my salary for 600.")
client.make_request(Request.SALARY_RAISE, 600)
print("######## Client: I want to take a vacation for 12 days.")
client.make_request(Request.VACATION, 12)
print("######## Client: I want to raise my salary for 1200.")
client.make_request(Request.SALARY_RAISE, 1200)

命令模式

内容

命令模式可将请求转换为一个包含与请求相关的所有信息的独立对象,这个独立对象实现了调用者与接收者之间的解耦,命令模式最大的杀手锏是它能非常轻松的实现撤销操作。

主要的应用场景就是GUI,将每一个具体命令封装称为一个类,使用不同的命令则调用不同的类,参数通过类进行传递。

这里省略

迭代器模式

内容

迭代器模式的关键思想是将对列表的访问和遍历从列表对象中分离出来,放入一个独立的迭代器对象中。迭代器类定义了访问该列表元素的接口。迭代器类提供的方法负责跟踪当前的元素,即它知道哪些元素已经遍历过了,哪些元素还没有被遍历。

迭代器模式能够提供一种方法按照顺序访问一个聚合对象中的所有元素,而又不需要暴露该对象的内部表示。

python中提供了iter()方法实现,这里略过

中介者模式

内容

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。

主要使用在多个类相互耦合,形成了网状结构时,将上述网状结构分离为星型结构。

角色:

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

适用场景

  • 如机场调度系统(多个跑道、飞机、指挥塔之间的调度)
  • 路由系统;著名的MVC框架中,其中的C(Controller)就是M(Model)和V(View)的中介者。

实现

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
from abc import ABC, abstractmethod


# 抽象中介者
class Mediator(ABC):
def __init__(self):
self.x = 0

@abstractmethod
def register(self, colleague):
pass

@abstractmethod
def show(self):
pass


# 抽象同事类
class Colleague:
def __init__(self, name, x):
self.name = name
self.x = x


# 具体同事类 汽车
class ConcreteColleagueCar(Colleague):
pass


# 具体同事类 鸟
class ConcreteColleagueBird(Colleague):
pass


# 具体中介类
class ConcreteMediator(Mediator):
collections = []

def register(self, colleague):
self.collections.append(colleague)

def show(self):
for colleague in self.collections:
print(colleague.name, colleague.x)
print(f'place x is {self.x} - {self.x + colleague.x}')
self.x = self.x + colleague.x + 1


if __name__ == '__main__':
mediator = ConcreteMediator()
car1 = ConcreteColleagueCar('car1', 1)
car2 = ConcreteColleagueCar('car2', 1)
bird1 = ConcreteColleagueBird('bird1', 2)
bird2 = ConcreteColleagueBird('bird2', 2)
mediator.register(car1)
mediator.register(car2)
mediator.register(bird1)
mediator.register(bird2)

mediator.show()

优点

  1. 通过让对象彼此解耦,增加对象的复用性
  2. 通过将控制逻辑集中,可以简化系统维护
  3. 通过中介者使一对多变成了一对一,便于理解
  4. 符合迪米特原则

缺点

  1. 在具体中介者类中包含了同事之间的交互细节,如果设计不好,引入中介者会使程序变的复杂
  2. 中介者承担过多责任,是中心化模式,若中介者出现了问题,整个系统将会崩溃

扩展

一个被Eclipse,NetBean等项目使用的设计模式

备忘录模式

内容

备忘录模式(Memento Pattern)用于保存一个对象的某个状态,以便在适当的时候恢复对象。特点是不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
跟原型模式很像,不过在原型模式中保存对象的一切,而备忘录模式中只保存恢复时需要的数据。

角色:

  • Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。
  • Memento(备忘录)
    :负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。
  • Caretaker(管理者): 负责备忘录Memento,不能对Memento的内容进行访问或者操作。

适用场景

  1. 需要保存和恢复数据的相关状态场景。如保存游戏状态的场景;撤销场景,如Ctrl-Z操作;事务回滚的应用。一般情况下事务回滚有两种方式:一是把从恢复点开始的操作都反向执行一遍;二是直接恢复到恢复点的各种状态。两种方式各有优缺点,要结合业务场景,决定使用哪种模式;
  2. 副本监控场景。备忘录可以当作一个临时的副本监控,实现非实时和准实时的监控。

实现

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from abc import ABC, abstractmethod
from datetime import datetime


class Originator(ABC):
@abstractmethod
def save(self):
pass

@abstractmethod
def restore(self, memento):
pass

@abstractmethod
def show(self):
pass

@abstractmethod
def set(self, state):
pass

@abstractmethod
def get(self):
pass


class ConcreteOriginator(Originator):
def __init__(self):
self._state = None

def save(self):
return ConcreteMemento(self._state)

def restore(self, memento):
self._state = memento.get_state()

def show(self):
print(f"State: {self._state}")

def set(self, state):
self._state = state

def get(self):
return self._state


class Memento(ABC):
@abstractmethod
def get_name(self):
pass

@abstractmethod
def get_date(self):
pass


class ConcreteMemento(Memento):
def __init__(self, state):
self._state = state
self._date = str(datetime.now())[:19]

def get_state(self):
return self._state

def get_name(self):
return f"{self._date} / ({self._state[0:9]}...)"

def get_date(self):
return self._date


class Caretaker:
def __init__(self, originator):
self._mementos = []
self._originator = originator

def backup(self):
print("Caretaker: Saving Originator's state...")
self._mementos.append(self._originator.save())

def undo(self):
if not len(self._mementos):
return

memento = self._mementos.pop()
print(f"Caretaker: Restoring state to: {memento.get_name()}")
try:
self._originator.restore(memento)
except Exception:
self.undo()

def show_history(self):
print("Caretaker: Here's the list of mementos:")
for memento in self._mementos:
print(memento.get_name())


if __name__ == "__main__":
originator = ConcreteOriginator()
caretaker = Caretaker(originator)

originator.set("State #1")
originator.set("State #2")
caretaker.backup()

originator.set("State #3")
caretaker.backup()

originator.set("State #4")
caretaker.backup()

caretaker.undo()
caretaker.undo()

caretaker.show_history()

print("\nClient: Now, let's rollback!\n")
caretaker.undo()

originator.show()

# Output:
# Caretaker: Saving Originator's state...
# Caretaker: Saving Originator's state...
# Caretaker: Restoring state to: 2020-07-07 22:07:54 / (State #4...)
# Caretaker: Restoring state to: 2020-07-07 22:07:54 / (State #3...)
# Caretaker: Here's the list of mementos:
# 2020-07-07 22:07:54 / (State #2...)
#
# Client: Now, let's rollback!
#
# Caretaker: Restoring state to: 2020-07-07 22:07:54 / (State #2...)
# State: State #2

优点

  1. 有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,
    使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。
  2. 本模式简化了发起人的类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需
    要的这些状态的版本。
  3. 当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

缺点

  1. 如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
  2. 当负责人角色将一个备忘录存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。
  3. 当发起人角色的状态改变的时候,有可能这个协议无效。

扩展

用Java实现Redis的RDB机制 离不开这个设计模式

观察者模式

内容

观察者模式应用比较广泛,又被称为“发布-订阅”模式。它用来定义对象间一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。

角色:

  • 抽象主题(Subject)
  • 具体主题(ConcreteSubject)– 发布者
  • 抽象观察者(Observer)
  • 具体观察者(ConcreteObserver)– 订阅者

使用场景

  • 当一个抽象模型有两个方面,其中一个方面依赖另一个方面。将这两者封装在独立对象中以使它们可以各自独立地改变和复用
  • 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象待改变
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧耦合的。

实现

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from abc import ABC, abstractmethod


# 抽象主题
class Subject(object):
@abstractmethod
def register(self, observer):
pass

@abstractmethod
def unregister(self, observer):
pass

@abstractmethod
def notify(self, notice):
pass


# 抽象的订阅者
class Observer(ABC):
@abstractmethod
def update(self, notice):
"""
:param notice: Notice类的对象
:return:
"""
pass


# 具体主题, 微信服务号
class ConcreteSubject(Subject):
def __init__(self, service_name):
self._users = []

# 服务号名称
self.service_name = service_name

# 添加订阅者信息
def register(self, observer):
if observer not in self._users:
self._users.append(observer)

# 用户取消关注,取消订阅者
def unregister(self, observer):
self._users.remove(observer)

# 通知所有订阅者
def notify(self, notice):
for observer in self._users:
observer.update(notice)

# 发布消息
def write_new_post(self, post_name):
print('微信服务号{}:{}'.format(self.service_name, post_name))
self.notify(post_name)


# 具体订阅者,微信用户
class ConcreteObserver(Observer):
def __init__(self, username):
self.username = username

def update(self, post_name):
print(f'微信用户{self.username}接收到消息,公众号发布了新帖子:{post_name}')


# 程序入口
if __name__ == "__main__":
# 微信服务号
python_service = ConcreteSubject("Python论坛")
# 用户对象
user1 = ConcreteObserver('cd')
user2 = ConcreteObserver('yx')
user3 = ConcreteObserver('xl')
# 订阅Python论坛公帐号
python_service.register(user1)
python_service.register(user2)
python_service.register(user3)
# 公众号发布文章通知所有订阅
python_service.write_new_post("python设计模式01")
# 用户2取消订阅旅游知识号
python_service.unregister(user2)
# 取消订阅的用户接收不到通知
python_service.write_new_post("python设计模式02")

# Output
# 微信服务号Python论坛:python设计模式01
# 微信用户cd接收到消息,公众号发布了新帖子:python设计模式01
# 微信用户yx接收到消息,公众号发布了新帖子:python设计模式01
# 微信用户xl接收到消息,公众号发布了新帖子:python设计模式01
# 微信服务号Python论坛:python设计模式02
# 微信用户cd接收到消息,公众号发布了新帖子:python设计模式02
# 微信用户xl接收到消息,公众号发布了新帖子:python设计模式02

优点

  1. 目标和观察者之间的抽象耦合最小
  2. 支持广播通信。

扩展

  • 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
  • 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接,
    使得部分对象可作为其他对象的附属发挥作用。
  • 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。
    当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
  • 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同,
    但这仍是一种中介者模式。
  • 假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象,
    而只有一些分布式的观察者。

状态模式

内容

行为模式关注的是对象的响应性。它们通过对象之间的交互以实现更强大的功能。状态设计模式是一种行为设计模式,有时也被称为状态模式对象。在此模式中,一个对象可以基于其内部状态封装多个行为。状态模式也可以看作是在运行时改变对象行为的一种方式。

状态设计模式允许对象在其内部状态变化时改变其行为。这看起来就像对象本身已经改变了它的类一样。状态设计模式常用于开发有限状态机,并帮助协调状态处理操作。

角色:

  • State:这被认为是封装对象行为的接口。这个行为与对象的状态相关联。
  • ConcreteState:这是实现State接口的子类。ConcreteState实现对象的特定状态相关联的实际行为
  • Context:这定义了客户感兴趣的接口。Context还维护一个ConcreteState子类的实例,该子类在内部定义了对象的特定状态的实现

实现

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from abc import ABC, abstractmethod


class State(ABC):
@abstractmethod
def handle(self):
pass


class ConcreteStateA(State):
def handle(self):
print("ConcreteStateA")


class ConcreteStateB(State):
def handle(self):
print("ConcreteStateB")


class Context(State):
def __init__(self):
self.state = None

def get_state(self):
return self.state

def set_state(self, state):
self.state = state

def handle(self):
self.state.handle()


context = Context()
state = ConcreteStateA()
stateB = ConcreteStateB()
context.set_state(state)
context.handle()
state = ConcreteStateB()
context.set_state(state)
context.handle()


# 计算机开关机状态
class ComputerState:
name = 'state'
allowed = []

def switch(self, state):
if state.name in self.allowed:
print('Current:', self, '=>switched to new state', state.name)
else:
print('Current:', self, '=>switched to', state.name, 'not possible')

def __str__(self):
return self.name


class Off(ComputerState):
name = 'off'
allowed = ['on']


class On(ComputerState):
name = 'on'
allowed = ['off', 'suspend', 'hibernate']


class Suspend(ComputerState):
name = 'suspend'
allowed = ['on']


class Hibernate(ComputerState):
name = 'hibernate'
allowed = ['on']


class Computer(object):
def __init__(self, model='HP'):
self.model = model
self.state = Off()

def change(self, state):
self.state.switch(state)


if __name__ == '__main__':
comp = Computer()
comp.change(On)
comp.change(Off)
comp.change(On)
comp.change(Suspend)
comp.change(Hibernate)
comp.change(On)
comp.change(Off)

优点

  • 在状态设计模式中,对象的行为是其状态的函数结果,并且行为在运动时根据状态而改变。这消除了if/else或switch/case条件逻辑的依赖。
  • 使用状态模式,实现多态行为的好处显而易见的,并且更易于添加状态来支持额外的行为
  • 状态设计还提高了聚合性,因为特定于状态的行为被聚合到ConcreteState类中,并且放置在代码中的同一个地方
  • 使用状态设计模式,通过只添加一个ConcreteState类来添加行为是非常容易的。因此,状态模式不仅改善了扩展应用程序行为时的灵活性,而且全面提高了代码的可维护性。

缺点

  • 类爆炸:由于每个状态都需要在ConcreteState的帮助下定义,因此我们可能导致创建了太多功能比较单一的类。我们不妨考虑有限状态机的情况——如果有许多状态,但是每个状态与另一个状态没有太大的不同,我们仍然需要将它们写成单独的ConcreteState类。这即增加了代码量,又使得状态机构更加难以审查。
  • 随着每个新行为的引入,Context类都需要进行相应的更新处理每个行为。这使得上下文行为更容易受到每个新的行为的影响。

扩展

五分钟学设计模式.09.状态模式

策略模式

内容

定义一系列的算法,把它们封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化。

角色:

  • 抽象策略 Strategy:定义了一个共同的接口。所有具体的算法类实现这个接口。环境(上下文)类 Context 使用这个接口调用具体的算法类。
  • 具体策略 ConcreteStrategy:封装了具体的算法,实现同一个接口。
  • 上下文 Context:环境(上下文)类。用于配置一个具体的算法策略对象,维持一个策略接口类型的参考(Reference),并且可以定义一个让接口
    Strategy 的具体对象访问的接口。在简单情况下,Context 类可以省略。

使用场景

在以下情况之一发生时可以使用策略模式。

  • 当有多个仅在行为上不同但是相关的类存在时,策略模式提供了一个为一个类配置多种行为之一的方法。
  • 当一个算法使用用户不应该知道的数据时,使用策略模式可以将算法的实现细节隐藏起来,避免暴露与算法相关的复杂细节。注意,虽然可以将算法的实现细节封装起来,但是客户程序必须知道各个策略子类的接口。
  • 当一个类有多种行为,这些行为以大块的条件语句实现时可以使用策略模式,这时可以将条件块移入它们自己的 Strategy 类。

实现

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
from abc import ABC, abstractmethod
from datetime import datetime


# 抽象策略
class Strategy(ABC):
@abstractmethod
def execute(self, data):
pass


# 具体策略
class CStrategy(Strategy):
def execute(self, data):
print(f"使用C算法处理{data}")


# 具体策略
class NStrategy(Strategy):
def execute(self, data):
print(F"使用N算法处理{data}")


# 上下文
class Context:
def __init__(self, strategy, data):
self.data = data
self.strategy = strategy
# 可以定义用户不知道的东西
self.date = datetime.now()

def set_strategy(self, strategy):
self.strategy = strategy

def do_strategy(self):
self.strategy.execute(self.data)

# Client
data = "Hello!"
context = Context(None, data)

# 使用C算法处理
context.set_strategy(CStrategy())
context.do_strategy()
# 使用N算法处理
context.set_strategy(NStrategy())
context.do_strategy()

优点

  1. 得到一系列可以复用的算法,这些算法继承一个共同的抽象类,因此共有的功能可以放到超类中。
  2. 将不同的算法封装在不同的策略子类中,使逻辑更加清晰,各个算法可以独立地变化,消除了大量的 if…else 语句。
  3. 提供了相同行为的不同实现。客户可以根据不同时间或空间要求选择不同的策略。
  4. 使功能改变或者扩展更容易。具体地说,修改一个算法不必重新编译“Client”与“Context”类。增加一个新算法时,在应用程序暂时还不想使用该新算法的情况下,不必重新编译“Client”与“Context”类。

缺点

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这与“依赖倒置原则”是背道而驰的。
  2. 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
  3. 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
  4. 在客户类中通常存在许多与策略类各个分支相关的条件语句,用于选择产生策略子类对象,然后将这些对象传递给 Context 类,而
    Context 类则直接使用此对象调用策略模式的策略子类的方法。

访问者模式

内容

访问者模式是指作用于一个对象结构体上的元素的操作。访问者可以使用户在不改变该结构体中的类的基础上定义一个新的操作。

角色:

  • Visitor:为每个 Element 的对象声明一个访问操作。该访问操作的名字最好要包含被访问的类的名字,以便确认该访问操作是专门针对哪个具体的类,如 visitFamilyNoChildren 是专门为了服务类 FamilyNoChildren 的。
  • ConcreteVisitor:实现 Visitor 声明的运算。每个运算实现为对应的类的对象定义的算法的一部分。ConcreteVisitor 提供算法的环境并且存储其局部状态。
  • Element:定义了一些基本的方法,其中包含提供基本数据的方法,例如一些 get()与 set()方法。重要的是,每个 Element 子类都必须定义一个接收者方法,该方法以 Visitor 为参数类型:Accept(Visitor),其作用是为被访问者对象和访问者对象之间的交互提供接口。
  • ConcreteElement:具体的 Element 的子类,例如 ElementA,该类包含一个 accept 方法接收访问者对象。另外,该类还可能定义一些其他的方法以帮助访问者实现一些功能。
  • ObjectStructure:提供一个高层接口,允许访问者访问 Element 的子类。在该类中可以包含一个结构,例如 ArrayList、Vector 等,提供所要访问的 element 的列表。

使用场景

  • 当一个对象的结构中,包含有多种类型的具有不同接口的对象,且用户要在这些对象上进行依赖于具体的类的运算时,需要用到访问者模式。这就是为什么访问者模式要针对每个被访问的子类都设计一个不同的接口的原因。事实上,如果每个被访问的子类都有相同的接口,包括构造方法、其他方法、参数都一致,则访问者类只需要设计一个访问方法,在该方法中含有一个用于区别不同的被访问的子类的参数即可,例如可以使用被访问者基类作为参数类型。在对象的结构中包含有多种类型的有不同接口的对象时,各个不同的访问方法可能为访问所对应的类提供不同的参数类型。
  • 当有多个不同的并且互不相关的运算将作用到这些对象上,而用户不希望这些运算混淆这些类时,可以使用访问者模式将相关的操作放到独立的类中,例如为了实现每个结点类中的计算价格方法,可以将所有的计算价格方法放到一个 VisitPrice 类中。
  • 在对象的数据类型很少改变,但是需要经常改变操作或者增加新的操作的情况下,可以使用访问者模式。反之,如果 Element 的子类经常改变结构,例如需要增加一个新的税种,这就需要在访问者类中增加新的访问方法,因此,在这种情况下使用访问者模式代价较高,尽量不要使用访问者模式。

实现

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
from abc import ABC, abstractmethod


class Visitor(ABC):
@abstractmethod
def visit_element_a(self, element_a):
pass

@abstractmethod
def visit_element_b(self, element_b):
pass


class ConcreteVisitor1(Visitor):
def visit_element_a(self, element_a):
print('ConcreteVisitor1', 'element_a')

def visit_element_b(self, element_b):
print('ConcreteVisitor1', 'element_b')


class ConcreteVisitor2(Visitor):
def visit_element_a(self, element_a):
print('ConcreteVisitor2', 'element_a')

def visit_element_b(self, element_b):
print('ConcreteVisitor2', 'element_b')


class Element(ABC):
@abstractmethod
def accept(self, visitor):
pass


class ElementA(Element):
def accept(self, visitor):
visitor.visit_element_a(self)


class ElementB(Element):
def accept(self, visitor):
visitor.visit_element_b(self)


class ObjectStructure:
def __init__(self):
self.elements = [ElementA(), ElementB()]


class Client:
@staticmethod
def main():
object_structure = ObjectStructure()
visitor = ConcreteVisitor1()
for element in object_structure.elements:
element.accept(visitor)
visitor = ConcreteVisitor2()
for element in object_structure.elements:
element.accept(visitor)


if __name__ == '__main__':
Client.main()

优点

  1. 使得在访问者类中针对复杂类结构中的某个类添加新方法较为容易,即只需要简单地添加一个新的访问者方法即可。如果不采用访问者模式,这需要在每个类中添加一个新的方法。
  2. 访问者将相关的方法集中在一个具体的访问者类中,而其他相关的方法集中在另外一个具体的访问者类中。也就是说,访问者子类是按照方法的类型来分类的。

缺点

在增加新的元素类的时候比较困难。每增加一个新的元素类,所有的访问者类都要增加一个新的方法,这违背了“开闭原则”。

模板方法模式

内容

定义一个操作中的算法骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

角色:

  • 抽象类 AbstractClass:声明一个定义算法步骤的接口,作用是定义抽象类(钩子操作),实现一个模板方法作为算法的骨架
  • 具体类 ConcreteClass:定义子类特定的步骤,作用是实现原子操作。

使用场景

  • 一次性实现一个算法的不变部分,各个子类中的公共行为应该被提取出来并集中到一个公共父类中以避免代码重复
  • 控制子类扩展
  • 当多个算法或类实现类似或相同逻辑的时候
  • 在子类中实现算法有助于减少重复代码的时候
  • 可以让子类利用覆盖实现行为来定义多个算法的时候

实现

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
75
from abc import ABC, abstractmethod


class AbstractClass(ABC):
@abstractmethod
def parse(self):
pass

@abstractmethod
def allocate(self):
pass

@abstractmethod
def preprocessing(self):
pass

@abstractmethod
def training(self):
pass

@abstractmethod
def inference(self):
pass

@abstractmethod
def saving_result(self):
pass

def template_method(self):
print("算法流程")
print("1. 解析数据")
self.parse()
print("2. 分配资源")
self.allocate()
print("3. 预处理")
self.preprocessing()
print("4. 训练")
self.training()
print("5. 推理")
self.inference()
print("6. 保存结果")
self.saving_result()


class ConcreteClass(AbstractClass):
def parse(self):
print("解析数据")

def allocate(self):
print("分配资源")

def preprocessing(self):
print("预处理")

def training(self):
print("训练")

def inference(self):
print("推理")

def saving_result(self):
print("保存结果")


class Client(object):
def __init__(self):
# 应该通过命令、参数等可配置不同的具体的类,这里为了简单直接实例化
self.concreate = ConcreteClass()

def main(self):
self.concreate.template_method()


client = Client()
client.main()

优点

  1. 没有重复代码
  2. 由于模板方法模式使用继承而不是合成,因此能够对代码进行重用。所以,只有为数不多的几个方法需要重写。
  3. 灵活性允许子类决定如何实现算法中的步骤。

缺点

  1. 调试和理解模板方法模式中的流程序列有时会令人困惑。你最终实现的方法可能是一个不应该实现的方法,或根本没有实现抽象方法。文档和严格的错误处理必须由程序员完成。
  2. 模板框架的维护可能是一个问题,因为任何层次(低层或高层)的变更都可能对现实造成干扰。因此使用模板方法模式可能使维护变得异常痛苦。
文章目录
  1. 1. 行为型模式
    1. 1.1. 解释器模式
      1. 1.1.1. 内容
    2. 1.2. 责任链模式
      1. 1.2.1. 内容
      2. 1.2.2. 角色:
      3. 1.2.3. 适用场景
      4. 1.2.4. 实现
    3. 1.3. 命令模式
      1. 1.3.1. 内容
    4. 1.4. 迭代器模式
      1. 1.4.1. 内容
    5. 1.5. 中介者模式
      1. 1.5.1. 内容
      2. 1.5.2. 角色:
      3. 1.5.3. 适用场景
      4. 1.5.4. 实现
      5. 1.5.5. 优点
      6. 1.5.6. 缺点
      7. 1.5.7. 扩展
    6. 1.6. 备忘录模式
      1. 1.6.1. 内容
      2. 1.6.2. 角色:
      3. 1.6.3. 适用场景
      4. 1.6.4. 实现
      5. 1.6.5. 优点
      6. 1.6.6. 缺点
      7. 1.6.7. 扩展
    7. 1.7. 观察者模式
      1. 1.7.1. 内容
      2. 1.7.2. 角色:
      3. 1.7.3. 使用场景
      4. 1.7.4. 实现
      5. 1.7.5. 优点
      6. 1.7.6. 扩展
    8. 1.8. 状态模式
      1. 1.8.1. 内容
      2. 1.8.2. 角色:
      3. 1.8.3. 实现
      4. 1.8.4. 优点
      5. 1.8.5. 缺点
      6. 1.8.6. 扩展
    9. 1.9. 策略模式
      1. 1.9.1. 内容
      2. 1.9.2. 角色:
      3. 1.9.3. 使用场景
      4. 1.9.4. 实现
      5. 1.9.5. 优点
      6. 1.9.6. 缺点
    10. 1.10. 访问者模式
      1. 1.10.1. 内容
      2. 1.10.2. 角色:
      3. 1.10.3. 使用场景
      4. 1.10.4. 实现
      5. 1.10.5. 优点
      6. 1.10.6. 缺点
    11. 1.11. 模板方法模式
      1. 1.11.1. 内容
      2. 1.11.2. 角色:
      3. 1.11.3. 使用场景
      4. 1.11.4. 实现
      5. 1.11.5. 优点
      6. 1.11.6. 缺点