模板实例
让我们创建一个汽车销售报告单作为例子。我们能在SQLite数据库表中储存销售记录。SQLite是一个基于文件的简单数据引擎,它允许我们是用SQL语法来储存记录。Python3 将SQLite包含在其标准库中。
现在我们需要执行两个常见的任务:
- 选择所有销售的新车并以逗号分隔的格式输出到屏幕上;
- 输出一个以逗号分隔的包含所有销售人员和他们销售总额的列表,并且将其保存到一个可以导入到电子表格的文件当中。
这看起来是两个很不相同的任务,但是他们有一些共同的特征。在这两个任务中,我们都需要执行如下步骤:
- 连接数据库;
- 构造一个查询新车或销售总额的查询函数;
- 发出查询请求;
- 将结果格式化为一个以逗号分隔的字符串;
- 输出数据到一个或电子邮件中。
这两个任务查询函数的构建和输出步骤是不同的,但是它们的其他步骤是相同的。我们可以使用模板模式将相同步骤放在一个基类中,而将不同步骤放在两个子类中。
在开始之前,我们构建一个数据库,把一些示例数据放进去:在这里我们已经创建了一个表来保存数据,并使用了6个插入语句来添加销售记录。数据储存在一个名为sales.db的文件中。现在数据样本创建好了。
接着我们根据上面列出的任务步骤,我们就从定义包含着些步骤的基类开始。每一个步骤都有自己的方法(使它很容易有选择的重写任何一步),我们有一个更加普遍的方法叫作依次调用步骤。对于我们的例子来说,我们的两个雷之间有3个方法是相同的:
import sqlite3
class QueryTemplate(object):
def connect(self):
self.conn = sqlite3.connect('sales.db')
def construct_query(self):
raise NotImplementedError
def do_query(self):
results = self.conn.execute(self.query)
self.results = results.fetchall()
def format_results(self):
output = []
for row in self.results:
row = [str(i) for i in row]
output.append(", ").join(row)
self.formatted_results = "\n".join(output)
def output_results(self):
raise NotImplementedError
为帮助之类的实现,另外两个没有指定内容的方法会提示NotImplementedError
。在python中,这是一个指定抽象接口的常见方式。这种方法可以不包含任何实现(使用pass
),甚至可以完全不指定。然而,提示NotImplementedError
可以帮助程序员理解,这个方法是需要被子类重写的;当我们忘记实现这些方法时,一个空白或者是不存在的方法会使得我们在试图实现他们的时候很难鉴别和排错。
现在我们有了一个模板可以处理这些无聊的细节,但是它也是足够灵活的,能够允许各种各样的查询方式来对他进行执行和格式化。最棒的部分在于,如果我们想要将我们的SQLite数据库引擎变成另一种数据库引擎,只需要改变模板类就行了,不需要去改编子类。
现在,让我们来实现两个具体的子类:
import datetime
class NewVehiclesQuery(QueryTemplate):
def construct_query(self):
self.query = "select * from Sales where new='true'"
def output_results(self):
print(self.format_results)
class UserGrossQuery(QueryTemplate):
def construct_query(self):
self.query = ("select salesperson, sum(amt) "+
" from Sales group by salesperson")
def output_results(self):
filename = "gross_sales_{0}".format(
datetime.date.today().strftime("%Y%m%d"))
with open(filename, 'w') as outfile:
outfile.write(self.formatted_results)
这两个类实际上都是相当短的,思考一下他们所做的工作:连接到一个数据库,执行一个查询,格式化结果并输出。这个超类可以负责处理重复性的工作,但也可以很容易地让我们区分这些任务之间的不同步骤。更进一步,我们也可以很容易地改进基类提供的步骤。例如,如果我们想要输出一个格式有别于逗号分隔的字符串(例如,将一个HTML报告上传到一个网站上),我们依然可以重写format_results。
参考:
《Python3 面向对象编程》