python设计模式之模板模式
python设计模式之模板模式
编写优秀代码的一个要素是避免冗余。在面向对象编程中,方法和函数是我们用来避免编写冗余代码的重要工具。
现实中,我们没法始终写出100%通用的代码。许多算法都有一些(但并非全部)通用步骤。广度优先搜索( Breadth-First Search, BFS)和深度优先搜索( Depth-First Search, DFS)是其中不错的例子,这两个流行的算法应用于图搜索问题。函数bfs()和dfs()在start和end之间存在一条路径时返回一个元组(True, path);如果路径不存在,则返回(False, path)(此时,path为空)。
def bfs(graph, start, end):
path = []
visited = [start]
while visited:
current = visited.pop(0)
if current not in path:
path.append(current)
if current == end:
print(path)
return (True, path)
# 两个顶点不相连,则跳过
if current not in graph:
continue
visited = visited + graph[current]
return (False, path)
def dfs(graph, start, end):
path = []
visited = [start]
while visited:
current = visited.pop(0)
if current not in path:
path.append(current)
if current == end:
print(path)
return (True, path)
# 两个顶点不相连,则跳过
if current not in graph:
continue
visited = graph[current] + visited
return (False, path)
注意两个算法之间的相似点。仅有一处不同(已加粗),其余部分完全相同。稍后我们再回来讨论这个问题。
可以使用列表的字典结构来表示这个有向图。每个城市是字典中的一个键,列表的内容是从该城市始发的所有可能目的地。叶子顶点的城市(例如, Erfurt)使用一个空列表即可(无目的地)。
def main():
graph = {
'Frankfurt': ['Mannheim', 'Wurzburg', 'Kassel'],
'Mannheim': ['Karlsruhe'],
'Karlsruhe': ['Augsburg'],
'Augsburg': ['Munchen'],
'Wurzburg': ['Erfurt', 'Nurnberg'],
'Nurnberg': ['Stuttgart', 'Munchen'],
'Kassel': ['Munchen'],
'Erfurt': [],
'Stuttgart': [],
'Munchen': []
}
bfs_path = bfs(graph, 'Frankfurt', 'Nurnberg')
dfs_path = dfs(graph, 'Frankfurt', 'Nurnberg')
print('bfs Frankfurt-Nurnberg: {}'.format(bfs_path[1] if bfs_path[0]
else 'Not found'))
print('dfs Frankfurt-Nurnberg: {}'.format(dfs_path[1] if dfs_path[0]
else 'Not found'))
bfs_nopath = bfs(graph, 'Wurzburg', 'Kassel')
print('bfs Wurzburg-Kassel: {}'.format(bfs_nopath[1] if bfs_nopath[0] else
'Not found'))
dfs_nopath = dfs(graph, 'Wurzburg', 'Kassel')
print('dfs Wurzburg-Kassel: {}'.format(dfs_nopath[1] if dfs_nopath[0] else
'Not found'))
if __name__ == '__main__':
main()
从性质来看,结果并不能表明什么,因为DFS和BFS不能很好地处理加权图(权重完全被忽略了)。处理加权图更好的算法是( Dijkstra的)最短路径优先算法、 Bellman-Ford算法和A*算法等。然而,我们仍然希望按打算的那样遍历图。我们期望的算法输出是一个城市列表,这些城市是在搜索从Frankfurt到Nurnberg的路径时访问过的。
bfs Frankfurt-Nurnberg: ['Frankfurt', 'Mannheim', 'Wurzburg', 'Kassel', 'Karlsruhe',
'Erfurt', 'Nurnberg']
dfs Frankfurt-Nurnberg: ['Frankfurt', 'Mannheim', 'Karlsruhe', 'Augsburg', 'Munchen',
'Wurzburg', 'Erfurt', 'Nurnberg']
bfs Wurzburg-Kassel: Not found
dfs Wurzburg-Kassel: Not found
结果看起来没问题。 BFS按广度进行遍历, DFS则按深度进行遍历,两个算法都没返回任何非期望的结果。这样不错,但我们的代码仍然有一个问题,那就是冗余。两个算法之间仅有一处不同,但其余代码都写了两遍。
是的!这个问题可以通过模板设计模式( Template design pattern)来解决。这个模式关注的是消除代码冗余,其思想是我们应该无需改变算法结构就能重新定义一个算法的某些部分。
def traverse(graph, start, end, action):
path = []
visited = [start]
while visited:
current = visited.pop(0)
if current not in path:
path.append(current)
if current == end:
return (True, path)
# 两个顶点之间没有连接,则跳过
if current not in graph:
continue
visited = action(visited, graph[current])
return (False, path)
def extend_bfs_path(visited, current):
return visited + current
def extend_dfs_path(visited, current):
return current + visited
不再有bfs()和dfs()两个函数,我们将代码重构为使用单个traverse()函数。 traverse()函数实际上是一个模板函数。它接受一个参数action,该参数是一个“知道”如何延伸路径的函数。根据要使用的算法,我们可以传递extend_bfs_path()或extend_dfs_path()作为目标动作。
你也许会争论说,通过在traverse()内部添加一个条件来检测应该使用哪个遍历算法,也能达到相同的结果。下面的代码展示了这个思路。
BFS = 1
DFS = 2
def traverse(graph, start, end, algorithm):
path = []
visited = [start]
while visited:
current = visited.pop(0)
if current not in path:
path.append(current)
if current == end:
return (True, path)
# 顶点不相连,则跳过
if current not in graph:
continue
if algorithm == BFS:
visited = extend_bfs_path(visited, graph[current])
elif algorithm == DFS:
visited = extend_dfs_path(visited, graph[current])
else:
raise ValueError("No such algorithm")
return (False, path)
这个方案不足,如下:
- [ ] 它使得traverse()难以维护。如果添加第三种方式来延伸路径,就需要扩展traverse()的代码,再添加一个条件来检测是否使用新的路径延伸动作。更好的方案是traverse()能发挥作用却好像根本不知道应该执行哪个action,因为这样在traverse()中不要求什么特殊逻辑。
- [ ] 它仅对只有一行区别的算法有效。如果存在更多区别,那么与让本应归属action的具体细节污染traverse()函数相比,创建一个新函数会好得多。
- [ ] 它使得traverse()更慢。这是因为每次traverse()执行时,都需要显式地检测应该执行哪个遍历函数。
执行traverse()与执行dfs()或bfs()没什么大的不同。下面是一个示例。
bfs_path = traverse(graph, 'Frankfurt', 'Nurnberg', extend_bfs_path)
dfs_path = traverse(graph, 'Frankfurt', 'Nurnberg', extend_dfs_path)
print('bfs Frankfurt-Nurnberg: {}'.format(bfs_path[1] if bfs_path[0] else 'Not
found'))
print('dfs Frankfurt-Nurnberg: {}'.format(dfs_path[1] if dfs_path[0] else 'Not
found'))
输出如下:
bfs Frankfurt-Nurnberg: ['Frankfurt', 'Mannheim', 'Wurzburg', 'Kassel', 'Karlsruhe',
'Erfurt', 'Nurnberg']
dfs Frankfurt-Nurnberg: ['Frankfurt', 'Mannheim', 'Karlsruhe', 'Augsburg', 'Munchen',
'Wurzburg', 'Erfurt', 'Nurnberg']
bfs Wurzburg-Kassel: Not found
dfs Wurzburg-Kassel: Not found
1. 现实生活的例子
工人的日程,特别是对于同一个公司的工人而言,非常接近于模板设计模式。所有工人都遵从或多或少相同的例行流程,但例行流程的某些特定部分区别又很大。仅在继承对实现有益时,我们才使用它。如果没有实际益处,则可以忽略它,并使用命令和输入惯例。
2. 软件的例子
Python在cmd模块中使用了模板模式,该模块用于构建面向行的命令解释器。具体而言cmd.Cmd.cmdloop()实现了一个算法,持续地读取输入命令并将命令分发到动作方法。每次循环之前、之后做的事情以及命令解析部分始终是相同的。这也称为一个算法的不变部分。变化的是实际的动作方法(易变的部分)。
Python的asyncore模块也使用了模板模式,该模块用于实现异步套接字服务客户端/服务器。其中诸如asyncore.dispatcher.handle_connect_event和asyncore.dispatcher.handle_write_event()之类的方法仅包含通用代码。要执行特定于套接字的代码,这两个方法会执行handle_connect()方法。注意,执行的是一个特定于套接字的handle_connect(),
不是asyncore.dispatcher.handle_connect()。后者仅包含一条警告。可以使用inspect模块来查看,如下所示。
>>> python3
import inspect
import asyncore
inspect.getsource(asyncore.dispatcher.handle_connect)
" def handle_connect(self):\n self.log_info('unhandled connect
event', 'warning')\n
3. 应用案例
模板设计模式旨在消除代码重复。如果我们发现结构相近的(多个)算法中有重复代码,则可以把算法的不变(通用)部分留在一个模板方法/函数中,把易变(不同)的部分移到动作/钩子方法/函数中。
页码标注是一个不错的模板模式应用案例。一个页码标注算法可以分为一个抽象(不变的)部分和一个具体(易变的)部分。不变的部分关注的是最大行号/页号这部分内容。易变的部分则包含用于显示某个已分页特定页面的页眉和页脚的功能。
所有应用框架都利用了某种形式的模板模式。在使用框架来创建图形化应用时,通常是继承自一个类,并实现自定义行为。然而,在执行自定义行为之前,通常会调用一个模板方法,该方法实现了应用中一定相同的部分,比如绘制屏幕、处理事件循环、调整窗口大小并居中,等等。
4. 实现
我们将实现一个横幅生成器,想法很简单,将一段文本发送给一个函数,该函数要生成一个包含该文本的横幅。横幅有多种风格,比如点或虚线围绕文本。横幅生成器有一个默认风格,但应该能够使用我们自己提供的风格。
函数generate_banner()是我们的模板函数。它接受一个输入参数( msg,希望横幅包含的文本)和一个可选参数( style,希望使用的风格)。默认风格是dots_style,我们马上就能看到。 generate_banner()以一个简单的头部和尾部来包装带样式的文本。实际上,这个头部和尾部可以复杂得多,但在这里调用可以生成头部和尾部的函数来替代仅仅输出简单字符串也无不可。
def generate_banner(msg, style=dots_style):
print('-- start of banner --')
print(style(msg))
print('-- end of banner --\n\n')
默认的dots_style()简单地将msg首字母大写,并在其之前和之后输出10个点。
def dots_style(msg):
msg = msg.capitalize()
msg = '.' * 10 + msg + '.' * 10
return msg
该生成器支持的另一个风格是admire_style()。该风格以大写形式展示文本,并在文件的每个字符之间放入一个感叹号。
def admire_style(msg):
msg = msg.upper()
return '!'.join(msg)
接下来这个风格是我目前最喜欢的。 cow_style()风格使用cowpy模块生成随机ASCII码艺术字符,夸张地表现文本。如果你的系统中尚未安装cowpy,可以使用下面的命令来安装。
>>> pip3 install cowpy
cow_style()风格会执行cowpy的milk_random_cow()方法,该方法在每次cow_style()执行时用于生成一个随机的ASCII码艺术字符。
def cow_style(msg):
msg = cow.milk_random_cow(msg)
return msg
main()函数向横幅发送文本“happy coding”,并使用所有可用风格将横幅输出到标准输出。
def main():
msg = 'happy coding'
[generate_banner(msg, style) for style in (dots_style, admire_style, cow_style)]
完整代码:
from cowpy import cow
def dots_style(msg):
msg = msg.capitalize()
msg = '.' * 10 + msg + '.' * 10
return msg
def admire_style(msg):
msg = msg.upper()
return '!'.join(msg)
def cow_style(msg):
msg = cow.milk_random_cow(msg)
return msg
def generate_banner(msg, style=dots_style):
print('-- start of banner --')
print(style(msg))
print('-- end of banner --\n\n')
def main():
msg = 'happy coding'
[generate_banner(msg, style) for style in (dots_style, admire_style, cow_style)]
if __name__ == '__main__':
main()
输出如下。由于cowpy的随机性,你的cow_style()输出也许会有所不同。
-- start of banner --
..........Happy coding..........
-- end of banner --
-- start of banner --
H!A!P!P!Y! !C!O!D!I!N!G
-- end of banner --
-- start of banner --
______________
< Happy coding >
--------------
\
\ \_\_ _/_/
\ \__/
(xx)\_______
(__)\ )\/\
U ||----w |
|| ||
-- end of banner--
5. 小结
在实现结构相近的算法时,可以使用模板模式来消除冗余代码。具体实现方式是使用动作/钩子方法/函数来完成代码重复的消除,它们是Python中的一等公民。
python设计模式之模板模式的更多相关文章
- Python设计模式(10)-模板模式
class DbManager: def insert(self): pass def dele(self): pass class DbManager: def insert(self): pass ...
- python设计模式之解释器模式
python设计模式之解释器模式 对每个应用来说,至少有以下两种不同的用户分类. [ ] 基本用户:这类用户只希望能够凭直觉使用应用.他们不喜欢花太多时间配置或学习应用的内部.对他们来说,基本的用法就 ...
- Python设计模式之MVC模式
# -*- coding: utf-8 -*- # author:baoshan quotes = ('A man is not complete until he is married. Then ...
- 图解Java设计模式之模板模式
图解Java设计模式之模板模式 豆浆制作问题 模板方法模式基本介绍 模板方法模式原理类图 模板方法模式解决豆浆制作问题 模板方法模式的钩子方法 模板方法模式在Spring框架中的源码分析 模板方法模式 ...
- python设计模式之状态模式
python设计模式之状态模式 面向对象编程着力于在对象交互时改变它们的状态.在很多问题中,有限状态机(通常名为状态机)是一个非常方便的状态转换建模(并在必要时以数学方式形式化)工具.首先,什么是状态 ...
- python设计模式之命令模式
python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...
- python设计模式之外观模式
python设计模式之外观模式 系统会随着演化变得非常复杂,最终形成大量的(并且有时是令人迷惑的)类和交互,这种情况并不少见.许多情况下,我们并不想把这种复杂性暴露给客户端.外观设计模式有助于隐藏系统 ...
- python设计模式之原型模式
python设计模式之原型模式 对于原型模式而言,其中最主要的部分就是关于一个对象的复制,其中就包含两个方面:1.浅复制:2.深复制.具体的区别请看我相关的随笔.这里简略的说明一下,浅复制就等于对 ...
- python设计模式之建造者模式
python设计模式之建造者模式 建造者模式的适用范围:想要创建一个由多个部分组成的对象,而且它的构成需要一步接一步的完成.只有当各个部分都完成了,这个对象才完整.建造者模式表现为复杂对象的创建与 ...
随机推荐
- 树形dp 之 小胖守皇宫
题目描述 huyichen世子事件后,xuzhenyi成了皇上特聘的御前一品侍卫. 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状:有边相连的宫殿间可以互相望见.大内保卫森严,三步一岗,五步一 ...
- python监控服务器应用日志,推送钉钉机器人,实时关注日志异常
生产环境多台服务器上部署了多个应用,日志出现报错时,无法及时反馈到开发人员.部署一个大型的运维监控应用,不但耗资源,而且配置也不简单. 简简单单写个python脚本来监控服务器日志就简单多了,废话不多 ...
- centos 构建dns服务 dnsmasq
1 安装yum -y install dnsmasq开放udp tcp 53 端口2,修改配置文件 dnsmasq.conf# grep -Ev "^$|^[#;]" /etc/d ...
- Vue.js +pdf.js 处理响应pdf文件流数据,前端转图片预览不可下载
使用场景及原因 实际业务中,一些说明书或协议仅支持用户在线预览,为避免用户自行下载,并进行修改,引发纠纷,特将文件已文件流的形式,传给前端并转为图片显示,此时可能会有人问,为什么不直接在后端转图片,前 ...
- 题解 CF13E 【Holes】
这个题和$P3203\ $弹飞绵羊基本上完全一致 我的做法是用\(LCT\)维护信息,开一个节点\(fly\),表示到此节点时,小球会弹飞,那么查询弹多少次即为\(siz[fly]-1\) 最后一次落 ...
- python 结合redis 队列 做一个例子
结合redis 队列 做了一个例子 #!/usr/bin/env python # coding: utf-8 # @Time : 2018/12/21 0021 13:57 # @Site : # ...
- react native redux
redux可以解决, 程序中所有组件的状态统一管理, 从而使我们可以更加动态的,灵活的控制程序 React:数据管理使用props.stateRedux的主要思想:提供一个数据存储中心,可以供外部访问 ...
- EF批量插入太慢?那是你的姿势不对
大概所有的程序员应该都接触过批量插入的场景,我也相信任何的程序员都能写出可正常运行的批量插入的代码.但怎样实现一个高效.快速插入的批量插入功能呢? 由于每个人的工作履历,工作年限的不同,在实现这样的一 ...
- ~~网络编程(八):UDP~~
进击のpython ***** 网络编程--UDP 那现在看到这里的 这就是网络编程的最后一讲了 上面讲的都是关于TCP的编程方法 还记得TCP和UDP传输的区别吗? UDP简单就简单到它可以不借助管 ...
- 解决智慧城市发展困扰:Web 3D 智慧环卫 GIS 系统
前言 智慧环卫,依托物联网技术与移动互联网技术,对环卫管理所涉及到的人.车.物.事进行全过程实时管理,合理设计规划环卫管理模式,提升环卫作业质量,降低环卫运营成本,用数字评估和推动垃圾分类管理实效.智 ...