一、简介
模板方法模式是一种行为设计模式。它定义了操作(模板方法)的基本组合和控制过程,并将某些步骤(抽象方法)推迟到子类中。当使用不同的子类时,可以修改一些特定的步骤,而不改变操作的基本过程。该设计将特定步骤的具体实现与操作过程分开,实现代码的再利用和扩展,从而提高代码的质量和可维护性。
模板方法模式包括:
- 抽象:负责定义模板方法、基本方法和抽象方法。
- 模板方法:抽象类中定义的流程操作集,包括基本方法和抽象方法,包括一系列流程操作和条件控制。
- 基本方法:抽象类中已实现的方法。
- 抽象方法:抽象类中尚未实现的方法。
- 具体子类:实现抽象类中定义的抽象方法,即实现具体步骤。
模板方法模式的优点:
- 包装不变部分,扩展可变部分。模板方法模式将可变部分包装在抽象方法中,不变部分包装在基本方法中。这使得子类可以根据需要扩展可变部分,而不变部分仍然保持不变。
- 为了避免重复代码,抽象类中包含的基本方法可以避免子类重复实现相同的代码逻辑。
- 更好的扩展性,由于具体的实现是由子类完成的,因此可以在不影响模板方法本身的情况下,方便地扩展新的功能或改变实现方法。
模板方法模式的缺点:
- 类别很多,因为每个算法都需要一个抽象类和一个特定的子类来实现,当操作过程较多时,类的数量可能会急剧增加,从而提高代码的复杂性。
- 模板方法与子类实现的抽象方法密切相关。如果需要修改模板方法,可能涉及多个子类的修改。
简单列出一些模板方法模式的应用场景:
- 开发框架,框架通常定义一些通用模板,子类可以根据自己的具体需要细化模板的实现细节,如
Spring
中的JdbcTemplate、RestTemplate、RabbitTemplate、KafkaTemplate
等。 - 对于业务逻辑,我们可以对业务流程进行一些拆解,并将具体步骤改为子类实现。例如,在发送验证码的过程中,我们需要选择不同的制造商来发送验证码,但在发送验证码之前,检查、生成和保存验证码的逻辑是相同的。
例如,我们使用一个简单的短信代码作为模板方法模式的示例:
定义一个短信模板
/** * 发送短信模板 */public abstract class SmsTemplate { /** * 发送方法 * * @param mobile 手机号 */ public void send(String mobile) throws Exception { System.out.println(”检查用户是否在一分钟内发送过短信, mobile:" + mobile); if (checkUserReceiveInOneMinute(mobile)) { throw new Exception(“请等一分钟再试”); } String code = genCode(); if (manufacturer(mobile, code)) { System.out.println(”短信制造商成功发送短信, mobile:" + mobile + ",code=" + code); save2redis(mobile, code); } } /** * 模板方法由不同的制造商向手机发送短信 * @return */ abstract boolean manufacturer(String mobile, String code); /** * 检查手机号码是否在1分钟内收到验证码,如果您在1分钟内收到验证码,则无法发送验证码 * @param mobile * @return */ public boolean checkUserReceiveInOneMinute(String mobile) { return ...; } /** * 生成6位验证码 * @return */ public String genCode() { return "123456"; } /** * 将手机号+验证码存储在redis中,验证登录接口 * @param mobile * @param code */ public void save2redis(String mobile, String code) { ... }}
添加两个由不同制造商实现的子类
/** * 发送阿里云短信 */public class AliyunSmsSend extends SmsTemplate{ @Override boolean manufacturer(String mobile, String code) { System.out.println(“读阿里云短信配置”); System.out.println(创建阿里云发送短信客户端); System.out.println(阿里云成功发送短信); return true; }}/** * 发送腾讯云短信 */public class TencentSmsSend extends SmsTemplate { @Override boolean manufacturer(String mobile, String code) { System.out.println(“读腾讯云短信配置”); System.out.println(“创建腾讯云发送短信客户端”); System.out.println(腾讯云成功发送短信); return true; }}
在 Java 调用程序
public class Main { public static void main(String[] args) throws Exception { SmsTemplate smstemplate1 = new AliyunSmsSend(); smstemplate1.send("13333333333"); System.out.println("---------------------------"); SmsTemplate smstemplate2 = new TencentSmsSend(); smstemplate2.send("13333333333"); }}
输出如下:
检查用户是否在一分钟内发送过短信,mobile:13333333阅读阿里云短信配置创建阿里云发送短信客户阿里云发送短信制造商成功发送短信mobile:13333333333,code=123456---------------------------检查用户是否在一分钟内发送过短信,mobile:13333333阅读腾讯云短信配置创建腾讯云发送短信客户腾讯云发送短信制造商成功发送短信,mobile:13333333333,code=123456
让我们来看看模板方法模式的组成:
- 抽象类
SmsTemplate
定义了发送短信的基本流程操作
- 检查用户是否在发送前1分钟内收到短信,并保持不变。
- 生成验证码,不变部分。
- 将远程验证码发送到用户手机,这种抽象方法是由不同的子类实现的,可变部分。
- 如果发送成功,则保存到 redis 中间,不变部分。
- 具体子类
AliyunSmsSend、TencentSmsSend
继承抽象,实现抽象方法manufacturer(String mobile, String code)
,在过程中定义可变部分。 - 调用模板法
send(mobile)
,基本流程组合和条件控制在模板方法中完成。
在 Spring
模板方法模式的实现非常简单,我们只需要对上述模板进行处理 Java
代码示例的 AliyunSmsSend
类稍作改造,加上 @Component
注解就行,
/** * 发送阿里云短信 */@Componentpublic class AliyunSmsSend extends SmsTemplate{ @Override boolean manufacturer(String mobile, String code) { IUserService userService = SpringUtil.getBean(IUserService.class); System.out.println(“读阿里云短信配置”); System.out.println(创建阿里云发送短信客户端); System.out.println(阿里云成功发送短信); return true; }}
如果在 AliyunSmsSend
其他类别需要注入类别 bean
,通过 cn.hutool.extra.spring.SpringUtil.getBean(...)
获得相应的方法 bean
就行。
在Java8 函数表达式也可用于替换抽象方法,代码如下,
/** * 发送短信模板 */public class SmsTemplateLambda { /** * 发送短信 * @param mobile 手机号 * @param biFunction * @throws Exception */ public void send(String mobile, BiFunction<String, String, Boolean> biFunction) throws Exception { System.out.println(”检查用户是否在一分钟内发送过短信,mobile:" + mobile); if (checkUserReceiveInOneMinute(mobile)) { throw new Exception(“请等一分钟再试”); } String code = genCode(); if (biFunction.apply(mobile, code)) { System.out.println(”短信制造商成功发送短信,mobile:" + mobile + ",code=" + code); save2redis(mobile, code); } } ...}
通过 BiFunction
函数,在用户手机上发送不同厂家的短信代码 send(mobile)
分离处理的方法。
调用方法如下:
public static void main(String[] args) throws Exception { SmsTemplateLambda smsTemplateLambda = new SmsTemplateLambda(); smsTemplateLambda.send("1333333333", (s, s2) -> { System.out.println(“读阿里云短信配置”); System.out.println(创建阿里云发送短信客户端); System.out.println(阿里云成功发送短信); return true; }); smsTemplateLambda.send("1333333333", (s, s2) -> { System.out.println(“读腾讯云短信配置”); System.out.println(“创建腾讯云发送短信客户端”); System.out.println(腾讯云成功发送短信); return true; }); }
可以看出,我们只能调用它 SmsTemplateLambda
类的 send(mobile)
当该方法实现不同制造商向手机发送短信的具体逻辑时。其优点是,在添加模板方法时,不需要增加特定的子类实现,以减少子类的创建和降低子类实现成本。
模板方法模式是模板方法模式的核心思想和价值,通过定义一个过程的基本操作,即模板方法,将具体的实现步骤推迟到子类中,使子类能够灵活地实现可变行为。