建造者设计模式是创建型设计模式的一种。创建型设计模式处理对象创建的问题。
建造者设计模式,用来构建需要经过若干个建造步骤才能完成的对象创建。由建造者和指挥者组成,建造者负责对象各个部分的建造方法实现,指挥者负责按照一定的步骤调用建造方法完成对象创建。
应用场景
需求:
京东和百度分别提供了行人识别的开放API,我们需要两个行人识别API进行集成。两个API都是Restful风格,但是两者的输入格式和输出风格不同。百度API要求图片以base64编码输入,而京东API要求以文件流输入,输入方面一个采用了四个点坐标标识检测对象,另一个采用了一个点加长、宽的方式。集成工作需要统一两个api的输入,统一两者的输出。
先看看不使用设计模式的做法:
def jd_human_detect(img):
# 使用静态token
# 拼装京东API所需的参数
# 调用京东API,处理异常
# 处理成统一的输出风格
return result
def baidu_human_detect(img):
# 调用登录获取认证token
# 拼装百度API所需的参数
# 调用百度API,处理异常
# 处理成统一的输出风格
return result
在上面的代码中,使用了两个方法,把认证、数据准备、api调用与异常处理、结果处理等步骤写到方法里面。这里存在几个问题:
-
可读性差,由于多个步骤堆在一起,很难快速理清方法中进行了哪些操作,特别是过一段时间再来查看或者将代码交给别人维护时
-
可扩展性差,如果需要再引入另外一个类似的API(我们后来使用了自己训练的模型,其它机构提供的API),需要再写一个难读的方法
-
不方便统一日志记录,当api调用失败时我们希望知道问题出在哪个环节,使用上述代码只能在每个方法里添加日志记录语句
-
不方便测试,由于很多环节堆在一起,无法对指定环节进行针对性测试
再看一下使用建造者模式重构后的代码:
class JDDetector(object):
"""建造者,京东API"""
def __init__(self):
self.name="京东API"
# 初始化配置信息
def login(self):
pass
def prepare_input(self, img):
# 准备输入参数
def detect(self, img):
# 完成api调用动作
def get_result(self, **kwargs):
# 完成结果统一操作
class BaiduDetector(object):
"""建造者,京东API"""
def __init__(self):
self.name="百度API"
# 初始化配置信息
def login(self):
# 完成登录
def prepare_input(self, img):
# 准备输入参数
def detect(self):
# 完成api调用动作
def get_result(self, **kwargs):
# 完成结果统一操作
def detect(img, api_type):
"""指挥者,行人检测"""
if api_type=='jd':
detector = JDDetector()
elif api_type=='baidu':
detector = BaiduDetector()
else:
raise ValueError('不支持的api:%s'%api_type)
logging.debug("%s开始登录"%detector.name)
detector.login()
logging.debug("%s完成登录"%detector.name)
logging.debug("%s开始准备输入参数"%detector.name)
detector.prepare_input(img)
logging.debug("%s完成准备输入参数"%detector.name)
logging.debug("%s开始调用API"%detector.name)
detector.detect()
logging.debug("%s完成调用API"%detector.name)
logging.debug("%s开始准备结果"%detector.name)
result = detector.get_result()
logging.debug("%s完成结果准备"%detector.name)
return result
为了方便展示建造者设计模式,上述代码略去了一些内容,例如两个创造者(
JDDetector
,BaiduDetector
)都采用了单例模式完成创建,指挥者(detect
)里面有一些异常处理保证异常信息的记录。
使用了建造者设计模式,代码一下子变的规矩多了,我们可以很清楚的从指挥者detect
那里看到api调用过程经历了哪些环节,而创造者JDDetector
和BaiduDetector
只需要专心实现自己的各个创造技能(方法)。当有一个新的接口需要接入时,我们只需要模仿现有的创造者进行实现,通过单元测试确认各个它的创建技能能完美工作就可以将他放到指挥者哪里应用起来。
这个例子中我们指挥者detect
操作建造者(JDDetector
,BaiduDetector
)创建了检测结果对象result
,由于result
实际上是python dict
的一个对象所以代码中给出没有result
对应类的实现。
网友评论