Scott's Blog

学则不固, 知则不惑

0%

Python 设计模式-策略与状态模式

这两种设计模式非常相似,它们的 UML 图都是完全相同的。策略模式注重于对算法的选择,而状态模式注重对状态的切换,可以理解为状态的切换会改变处理的策略。

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

策略模式

策略模式是一种面向对象编程中的抽象模式。针对同样的问题,它实现了不同的解决方案,你的代码可以在运行的时候自由选择最恰当的方案。

策略模式的一个应用是排序,比如你要实现一个排序算法,它可以自动选择排序方法来对输入的数组进行排序,排序的方法是自动选择的,而结果是不变的。

还有一个例子是设置电脑的墙纸,当你设置墙纸的时候,你的电脑会自动帮你设置很多事情:

  1. 根据屏幕的分辨率,自动将图片缩放到合适的大小
  2. 自动处理图片与系统组件之间的缩放、虚化关系
  3. 图片与背景色的结合

你可以定义不同的对象,它们接受的 input 是一样的(目标图片,屏幕分辨率),不管怎样,这些对象都都能达到设置屏幕壁纸的目的。

有人说我通过 if 判断也可以达到同样的目的,但是这意味着你需要将你的代码放到一个巨大的方法中,随着新的策略的增加,你的函数将变得非常笨重。

这里略过策略模式的实例。

状态模式

状态模式的目的是实现“状态切换”,对象的状态可以被外面知道,并且可能会被一些活动改变。来一个例子,需求是要对一个 xml 文件进行解析,一个简单的 xml 文件如下:

1
2
3
<body>
<title>welcome to scott's blog</title>
</body>

我们想把这样的文件解析出来,结果是我可以通过 node.attr 的方式访问 xml 文件的内容。比如 xml_file.body 作为一个节点。该节点有一个属性叫子节点,我可以通过 xml_file.body.children 的方式拿到,对于 title 中的内容,我已通过 xml_file.body.title.text 的方式拿到。

首先需要一个节点类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Node:
"""节点类,记录节点的名字,文本,节点之间有上下级关系,所以它
还需要一个指向父节点的指针。
"""
def __init__(self, tagname, parent=None):
self.parent=parent
self.tagname = tagname
# text 和 children 都由其他组件操作
self.children = []
self.text = ""

def __str__(self):
if self.text:
return self.tagname + ':' + self.text

else:
return self.tagname

有了节点后,我们还需要一个解析器,解析器会一层一层的解析字符串,我们可以定义解析器有几种状态,即处于:

  • 开始节点
  • 子节点
  • 结束节点

如何根据解析器的状态判断是否还可以深入一步呢?可以这么定义:

  • 开始节点
  • 子节点
  • 打开的节点
  • 结束节点
  • 文本节点(最底层无子节点的节点)

来看代码实现:

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
class Parser:
"""Parser 是一个解析器。它会负责去切换状态,下图的状态之间会互相转换。
第一个状态是 First Tag,它永远将切换至子节点,再由子节点来决定切换到其他哪个状态。
每一个状态都会用自己的方法处理收到的剩余字符,然后将状态再设置为 Children Node,告诉解析器来处理剩下的部分。
+----------------------------------------------------------+
| .-------------. |
| ( First Tag ) |
| `-------------' |
| | |
| | |
| .------v------. |
| +--------->( Children Node )<----------+ |
| | `-------------' | |
| | ^ | |
| v v v |
| .-------------. .-------------. .-------------. |
| ( Open tag ) ( Closed tag ) ( Text ) |
| `-------------' `-------------' `-------------' |
| |
+----------------------------------------------------------+
"""
def __init__(self, parse_str):
self.parse_str = parse_str
self.root = None
self.current_node = None
self.state = FirstTag()

def process(self, remain_str):
remainning = self.state.process(remain_str, self)
if remainning:
self.process(remainning)

def start(self):
self.process(self.parse_str)


class FirstTag:
def process(self, remain_str, parser):
"""处理剩余的字符。

Args:
remain_str (string): 剩余需要解析的字符
parser (Parser): parser 即上面定义的的 Parser,它会被闯进来修改它的属性,如current_node, root, state

Returns:
string: 剩余需要解析的字符
"""
i_start_tag = remain_str.find('<')
i_end_tag = remain_str.find('>')
tag_name = remain_str[i_start_tag+1:i_end_tag]

root = Node(tag_name)
parser.root = parser.current_node = root
parser.state = ChildrenNode()

return remain_str[i_end_tag+1:]


class ChildrenNode:
def process(self, remain_str, parser):
"""同样只需要一个 process 方法来处理字符串。
作为 ChildrenNode,它需要根据字符来判断需要使用什么样的类状态器,
类状态器的 process 方法将会完成对剩余字符的处理。

Args:
remain_str ([type]): [description]
parser ([type]): [description]
"""
striped = remain_str.strip()
if striped.startswith('<'):
parser.state = OpenTag()
elif striped.startswith('>'):
parser.state = CloseTag()
else:
parser.state = TextNode()


class OpenTag:
def process(self, remain_str, parser):
"""
"""
i_start_tag = remain_str.find('<')
i_end_tag = remain_str.find('>')
tag_name = remain_str[i_start_tag+1:i_end_tag]
# parser.current_node 未被更改还是上一层节点,将其设为父节点
node = Node(tag_name, parser.current_node)
parser.current_node.children.append(node)
parser.current_node = node
parser.state = ChildrenNode()

return remain_str[i_end_tag+1:]


class CloseTag:
def process(self, remain_str, parser):
i_start_tag = remain_str.find('<')
i_end_tag = remain_str.find('>')
# assert 断言中
# 第一个确保 < 后是/,如 </h1>
# 第二个确保以同一个tag名开始结束,如 <h1>Hi</h1>
# 以及因当前是结束tag,重置parser当前节点为其父节点
assert remain_str[i_start_tag+1] == '/'
tagname = remain_str[i_end_tag+2:i_end_tag]
assert tagname == parser.current_node.tagname
parser.current_node = parser.current_node.parent
parser.state = ChildrenNode()

return remain_str[i_end_tag+1:].strip()


class TextNode:
def process(self, remain_str, parser):
i_start_tag = remain_str.find('<')
text = remain_str[:i_start_tag]
parser.current_node.text = text
parser.state = ChildrenNode()

return remain_str[i_start_tag:]

启动代码

1
2
3
4
5
6
7
8
9
10
if __name__ == '__main__':
with open("data.xml") as file:
p = Parser(file.read())
p.start()

nodes = [p.root]
while nodes:
node = nodes.pop(0)
print(node)
nodes = node.children + nodes

将会产生下面的输出:

1
2
body
title:welcome to scott's blog