开发软件时一个常见的情况是有一个通用的算法,只是步骤上略有不同。我们希望不同的 实现能够遵守通用模式,保证它们使用了同一个算法,也是为了让代码更加易读。一旦你 从整体上理解了算法,就能更容易理解其各种实现。
模板方法模式是为这些情况设计的:整体算法的设计是一个抽象类,它有一系列抽象方法,代表算法中可被定制的步骤,同时这个类中包含了一些通用代码。算法的每一个变种 由具体的类实现,它们重写了抽象方法,提供了相应的实现。
让我们假想一个情境来搞明白这是怎么回事。假设我们是一家银行,需要对公众、公司和 职员放贷。放贷程序大体一致即验明身份、信用记录和收入记录。这些信息来源不一, 衡量标准也不一样。你可以查看一个家庭的账单来核对个人身份;公司都在官方机构注册 过,比如美国的SEC、英国的Companies House。
我们先使用一个抽象类LoanApplication 来控制算法结构,该类包含一些贷款调查结果 报告的通用代码。根据不同的申请人,有不同的类:CompanyLoanApplication、Personal LoanApplication 和EmployeeLoanApplication。图1 展示了LoanApplication 类的结构。
图1 使用模板方法模式描述申请贷款过程CompanyLoanApplication 的checkIdentity 方法在Companies House 等注册公司数据库中查找相关信息。checkIncomeHistory 方法评估公司的现有利润、损益表和资产负债表。checkCreditHistory 方法则查看现有的坏账和未偿债务。
PersonalLoanApplication 的checkIdentity 方法通过分析客户提供的纸本结算单,确认客户地址是否真实有效。checkIncomeHistory 方法通过检查工资条判断客户是否仍被雇佣。checkCreditHistory 方法则会将工作交给外部的信用卡支付提供商。
EmployeeLoanApplication 就是没有查阅员工历史功能的PersonalLoanApplication。为了方便起见,我们的银行在雇佣员工时会查阅所有员工的收入记录图 2。
图2 员工申请贷款是个人申请的一种特殊情况使用Lambda 表达式和方法引用,我们能换个角度思考模板方法模式,实现方式也跟以前不一样。模板方法模式真正要做的是将一组方法调用按一定顺序组织起来。如果用函数接口表示函数,用Lambda 表达式或者方法引用实现这些接口,相比使用继承构建算法,就会得到极大的灵活性。让我们看看如何使用这种方式实现LoanApplication 算法,请看图3
图3 员工申请贷款的例子正如读者所见, 这里没有使用一系列的抽象方法, 而是多出一些属性:identity、creditHistory 和incomeHistory。每一个属性都实现了函数接口Criteria,该接口检查一项标准,如果不达标就抛出一个问题域里的异常。我们也可以选择从check 方法返回一个类来表示成功或失败,但是沿用异常更加符合先前的实现图4。
图4 如果申请失败,函数接口Criteria 抛出异常采用这种方式,而不是基于继承的模式的好处是不需要在LoanApplication 及其子类中实现算法,分配功能时有了更大的灵活性。比如,我们想让Company 类负责所有的检查,那么Company 类就会多出一系列方法,如图5。
图5 Company 类中的检查方法现在只需为CompanyLoanApplication 类传入对应的方法引用,如图6。
图6 CompanyLoanApplication 类声明了对应的检查方法将行为分配给Company 类的原因是各个国家之间确认公司信息的方式不同。在英国,Companies House 规范了注册公司信息的地址,但在美国,各个州的政策是不一样的。
使用函数接口实现检查方法并没有排除继承的方式。我们可以显式地在这些类中使用Lambda 表达式或者方法引用。
我们也不需要强制EmployeeLoanApplication 继承PersonalLoanApplication 来达到复用,可以对同一个方法传递引用。它们之间是否天然存在继承关系取决于员工的借贷是否是普通人借贷这种特殊情况,或者是另外一种不同类型的借贷。因此,使用这种方式能让我们更加紧密地为问题建模。
网友评论