Scott's Blog

学则不固, 知则不惑

0%

Python 设计模式-模版模式

模版模式一般利用继承实现。比如我们有不同的任务要完成,其中有一些任务是相同的,我们可以将这些任务放在公共的基类中完成,其余特殊的任务设计在子类中。

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

模版模式有助于去除重复代码,并满足了”DRY-不要重复自己“原则。

平时遇到数据处理的需求,我们需要执行一些常见的任务,如:

  1. 去数据库取数,并将某些结果打印至控制台
  2. 去数据库取数,简单处理后,将结果保存到 csv 文件

在这两个步骤中,有些子步骤是一样的,比如连接数据库,发出查询请求,执行 query 语句等。

可以将这部分功能集成到基类中,而其他特殊的功能可以放到子类中。

我们举例来说明,首先准备一个数据库。

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

conn = sqlite3.connect("sales.db")
conn.execute(
"CREATE TABLE sales"
"("
"salespersion txt,"
"amt currency,"
"year integer,"
"model text,"
"new boolen"
")"
)

插入一些假数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# delete all rows from db before any new rows insert into table
conn.execute("delete from sales")

# insert rows
conn.execute(
"insert into sales values"
"('Scott', 1400, 2007, 'Toyota', 'false')"
)
conn.execute(
"insert into sales values"
"('Desmond', 1600, 2010, 'Honda fit', 'true')"
)
conn.execute(
"insert into sales values"
"('Burney', 1600, 2010, 'Honda fit', 'true')"
)
conn.execute(
"insert into sales values"
"('Austin', 1600, 2010, 'Honda fit', 'true')"
)

cursor = conn.execute("select * from sales")
cursor.fetchall()

查询结果输出如下:

1
2
3
4
[('Scott', 1400, 2007, 'Toyota', 'false'),
('Desmond', 1600, 2010, 'Honda fit', 'true'),
('Burney', 1600, 2010, 'Honda fit', 'true'),
('Austin', 1600, 2010, 'Honda fit', 'true')]

现在我们设计一个 query 的模版类,想想它需要有哪些功能?

一个可能的基类是这样的:

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
class QueryTemplate:
def connect(self):
pass


def construct_query(self):
pass


def do_query(self):
pass


def format_results(self):
pass


def output_results(self):
pass


def process_format(self):
"""暴露给外部调用的方法,用来保证每个方法按照顺序执行。
"""
self.connect()
self.construct_query()
self.do_query()
self.format_results()
self.output_results()

基类定义了我们需要有哪些功能,现在根据我们的需求,将其中一些可以共用的代码实现:

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
class QueryTemplate:
"""这个类提供了连接数据库、执行 query,格式化结果的功能
而对于构造 query、导出结果的功能,则抽象成接口交给子类去实现
"""
def connect(self):
self.conn = sqlite3.connect("sales.db")


def construct_query(self):
raise NotImplementedError()


def do_query(self):
result = self.conn.execute(self.query) # query 待 construct_query 实现
self.result = result.fetchall()


def format_result(self):
output = []
for row in self.result:
row = [str(i) for i in row]
output.append(row)
self.formated_result = '\n'.join(output)


def output_result(self):
raise NotImplementedError()

现在实现我们自己的需求就很容易了,因为我们只需要将精力放在构造 query 和 输出结果这两个方法上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NewVehiclesQuery(QueryTemplate):
def construct_query(self):
self.query = "select * from sales where new = 'true'"


def output_result(self):
print(self.format_result)


class OtherQuery(QueryTemplate):
def construct_query(self):
self.do_query = "select * from sales" # do what you want to do

def output_result(self):
file_name = "output_file.csv"
with open (file_name, 'w') as file:
file.write(self.formated_result)