4

设计模式之模板方法模式 - waynaqua

 11 months ago
source link: https://www.cnblogs.com/waynaqua/p/17442073.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

设计模式之模板方法模式

模板方法模式是一种行为型设计模式,它定义一个操作(模板方法)的基本组合与控制流程,将一些步骤(抽象方法)推迟到子类中,在使用时调用不同的子类,就可以达到不改变一个操作的基本流程情况下,即可修改其中的某些特定步骤。这种设计方式将特定步骤的具体实现与操作流程分离开来,实现了代码的复用和扩展,从而提高代码质量和可维护性。

模板方法模式包含以下:

  • 抽象类:负责定义模板方法、基本方法、抽象方法。
  • 模板方法:在抽象类中定义的流程操作集合,里面有一系列流程操作和条件控制,包含基本方法和抽象方法。
  • 基本方法:在抽象类中已经实现了的方法。
  • 抽象方法:在抽象类中还没有实现的方法。
  • 具体子类:实现抽象类中所定义的抽象方法,也就是实现特定步骤。

模板方法模式的优点:

  1. 封装不变部分,扩展可变部分。模板方法模式将可变的部分封装在抽象方法中,不变的部分封装在基本方法中。这使得子类可以根据需求对可变部分进行扩展,而不变部分仍然保持不变。
  2. 避免重复代码,抽象类中包含的基本方法可以避免子类重复实现相同的代码逻辑。
  3. 更好的扩展性,由于具体实现由子类来完成,因此可以方便地扩展新的功能或变更实现方式,同时不影响模板方法本身。

模板方法模式的缺点:

  1. 类多,由于每个算法都需要一个抽象类和具体子类来实现,因此在操作流程比较多时可能导致类的数量急剧增加,从而导致代码的复杂性提高。
  2. 关联性高,模板方法与子类实现的抽象方法紧密相关,如果该模板方法需要修改,可能会涉及到多个子类的修改。

简单列一些模板方法模式的应用场景:

  1. 开发框架,通常框架会定义一些通用的模板,子类可以根据自身的特定需求来细化模板的实现细节,比如 Spring 中的 JdbcTemplate、RestTemplate、RabbitTemplate、KafkaTemplate 等。
  2. 业务逻辑,我们可以针对业务流程做一些拆解,将特定步骤改为子类实现。比如发送验证码的流程,在发送验证码时需要选择不同厂商来发送验证码,但是我们发送的验证码前的检查、验证码生成、保存验证码逻辑都是一样的。

二、Java中实现模板方法模式

如上,我们用一个简单的发送短信代码来做模板方法模式的示例:

定义一个发送短信模板

/**
 * 发送短信模板
 */
public abstract class SmsTemplate {

    /**
     * 发送方法
     *
     * @param mobile 手机号
     */
    public void send(String mobile) throws Exception {
        System.out.println("检查用户一分钟内是否发送过短信,
                    mobile:" + mobile);
        if (checkUserReceiveInOneMinute(mobile)) {
            throw new Exception("请等待1分钟后重试");
        }
        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:13333333333
读取阿里云短信配置
创建阿里云发送短信客户端
阿里云发送短信成功
短信厂商发送短信成功,mobile:13333333333,code=123456
---------------------------
检查用户一分钟内是否发送过短信,mobile:13333333333
读取腾讯云短信配置
创建腾讯云发送短信客户端
腾讯云发送短信成功
短信厂商发送短信成功,mobile:13333333333,code=123456

我们来看看模板方法模式的组成:

  • 抽象类 SmsTemplate 中定义了发送短信的基本流程操作
    1. 发送前检查用户1分钟内是否接收过短信,不变部分。
    2. 生成验证码,不变部分。
    3. 发远验证码到用户手机,这个抽象方法由不同子类实现,可变部分
    4. 发送成功则保存到 redis 中,不变部分。
  • 具体子类 AliyunSmsSend、TencentSmsSend 继承抽象类,实现抽象方法 manufacturer(String mobile, String code),定义流程中的可变部分。
  • 调用模板方法 send(mobile) ,在模板方法中完成了基本流程组合与条件控制。

三、Spring 实现模板方法模式

Spring 中实现模板方法模式,是非常简单的,我们只需要对上述的 Java 代码示例的 AliyunSmsSend 类稍作改造,加上 @Component 注解就行,

/**
 * 阿里云短信发送
 */
@Component
public 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中Lambda表达式

在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("请等待1分钟后重试");
        }
        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) 方法时,才实现不同厂商发送短信到手机的具体逻辑。好处就是每增加一个模板方法时,不用增加具体的子类实现,减少类的创建与降低子类的实现成本。

模板方法模式通过定义一个流程基本操作也就是模板方法,将具体的实现步骤推迟到子类中,使得子类可以灵活地实现可变的行为,这是模板方法模式的核心思想与价值所在。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK