设计模式实战:模版方法

2023-09-16 19:35:46

1.模版方法概述

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

2.项目中的具体需求

项目中mongodb有多个日志类的表数据过多,不仅查询影响性能、还增加了存储成本,决定这些表只保留6个月的数据,大于6个月在2年零6个月内的数据放在备份表。超过两年零6个月的数据直接删除。
这里拿两个表举例,分别是:
nativeDO
successDO
备份表名称定义为:
nativeDO_history
successDO_history

这些表移动数据的步骤是固定的,分别为:原始表两年零6个月的直接删除->把原始表6个月外两年零6个月内的移动到历史表->删除历史表两年零6个月后的数据。我们把这个固定的步骤抽象出来。

3.代码实现

抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

  • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

@Slf4j
public abstract class HistoricalDataAbstractClass<T> {

    /**
   * 两年零6个月的直接删除
   *
   * @param timeYear
   * @param time
   * @return
   */
    protected abstract void delete(Long timeYear, Long time);


    /**
   * 6个月外两年零6个月内的移动到历史表
   * @param pageSize
   * @param time
   * @return
   */
    protected abstract PageResult<T> mobileData(Integer pageSize, Long time);

    /**
   * 删除历史表两年零6个月后的数据
   * @param timeYear
   * @param time
   */
    protected abstract void deleteByHistoryTable(Long timeYear, Long time);

    public final void historicalData(String jobName) {
        Integer pageSize = 2000;
        // 6个月前的时间戳,今日 2023-08-18 00:00:00那么结果就是2023-02-18 00:00:00
        long time =
        DateUtil.offset(
            DateUtil.parse(DateUtil.today()),
            DateField.MONTH,
            -BaseConstant.SAVE_DATA_FOR_SEVERAL_MONTHS)
        .getTime();
        // 2年6个月前的时间戳
        long timeYear =
        DateUtil.offset(
            DateUtil.parse(DateUtil.formatDate(new Date(time))),
            DateField.YEAR,
            -BaseConstant.SAVE_DATA_FOR_SEVERAL_YEAR)
        .getTime();
        try {
            //两年零6个月的直接删除
            delete(timeYear, time);
            //6个月外两年零6个月内的移动到历史表
            PageResult<T> page = mobileData(pageSize, time);
            Long total = page.getTotal();
            while (total > 0) {
                page = mobileData(pageSize, time);
                total = page.getTotal();
                //停止
                if (page.getList().size() < pageSize) {
                    break;
                }
            }
            //删除历史表两年零6个月后的数据
            deleteByHistoryTable(timeYear, time);
        } catch (Exception e) {
            log.error("historicalData " + jobName + " error", e);
        }
    }
}

具体子类(Concrete Class):实现抽象类中所定义的抽象方法。
这是一个操作nativeDO表的具体子类

@Slf4j
@Service
public class NativeHookRecordHistoricalDataServiceImpl
    extends HistoricalDataAbstractClass<NativeHookRecordDO>{

  @Resource private INativeHookRecordDbService nativeHookRecordDbService;

  /**
   * 两年零6个月的直接删除
   * @param timeYear
   * @param time
   */
  @Override
  protected void delete(Long timeYear, Long time) {
    log.info(
        "historicalData NativeHookRecordHistoricalDataJob Data processing time point:{}", time);
    nativeHookRecordDbService.createHistoricalDataCollection();
    DeleteResult nativeHookRecordDeleteResult = nativeHookRecordDbService.deleteByTime(timeYear);
    log.info(
        "historicalData NativeHookRecordHistoricalDataJob nativeHookRecordDeleteResult:{},time:{},Delete before this time:{}",
        nativeHookRecordDeleteResult,
        time,
        timeYear);
  }

  /**
   * 删除历史表两年零6个月后的数据
   * @param timeYear
   * @param time
   */
  @Override
  protected void deleteByHistoryTable(Long timeYear, Long time) {
    DeleteResult nativeHookRecordHistoryDeleteResult =
        nativeHookRecordDbService.deleteByHistoryTable(timeYear);
    log.info(
        "historicalData NativeHookRecordHistoricalDataJob nativeHookRecordHistoryDeleteResult:{},time:{},Delete before this time:{}",
        nativeHookRecordHistoryDeleteResult,
        time,
        timeYear);
  }

  /**
   * 6个月外两年零6个月内的移动到备份表
   * @param pageSize
   * @param time
   * @return
   */
  @Override
  protected PageResult<NativeHookRecordDO> mobileData(Integer pageSize, Long time) {
    PageResult<NativeHookRecordDO> page =
        nativeHookRecordDbService.findPagByTime(time, 1, pageSize);
    nativeHookRecordDbService.deleteById(page.getList());
    nativeHookRecordDbService.insertMultiple(page.getList());
    return page;
  }
}

这是一个操作successDO表的具体子类


@Slf4j
@Service
public class SuccessHookAddFieldServiceImpl 
	extends HistoricalDataAbstractClass<SuccessHookDO>{

  @Resource private SuccessHookDbServiceImpl successHookDbService;

  /**
   * 两年零6个月的直接删除
   * @param timeYear
   * @param time
   */
  @Override
  protected void delete(Long timeYear, Long time) {
    log.info("historicalData SuccessHookHistoricalDataJob Data processing time point:{}", time);
    successHookDbService.createHistoricalDataCollection();
    // 超过2年零6个月的数据直接删除
    DeleteResult successHookDeleteResult = successHookDbService.deleteByTime(timeYear);
    log.info(
        "historicalData SuccessHookHistoricalDataJob successHookDeleteResult:{},time:{},Delete before this time:{}",
        successHookDeleteResult,
        time,
        timeYear);
  }

  /**
   * 删除历史表两年零6个月后的数据
   * @param timeYear
   * @param time
   */
  @Override
  protected void deleteByHistoryTable(Long timeYear, Long time) {
    DeleteResult successHookHistoryDeleteResult =
        successHookDbService.deleteByTimeAndCollectionName(timeYear);
    log.info(
        "historicalData SuccessHookHistoricalDataJob successHookHistoryDeleteResult:{},time:{},Delete before this time:{}",
        successHookHistoryDeleteResult,
        time,
        timeYear);
  }

  /**
   * 6个月外两年零6个月内的移动到备份表
   * @param pageSize
   * @param time
   * @return
   */
  @Override
  protected PageResult<SuccessHookDO> mobileData(Integer pageSize, Long time) {
    PageResult<SuccessHookDO> page = successHookDbService.findPagByTime(time, 1, pageSize);
    successHookDbService.deleteById(page.getList());
    successHookDbService.insertMultiple(page.getList());
    return page;
  }

}

最后我们通过定时任务来调用


/**
 * 这个定时任务用于nativeDO表6个月的数据保存再nativeDO表, 6个月外两年零6一个月内的保存在nativeDO_history表
 * 两年零6一个月外的直接删除
 */
@Slf4j
@Data
@Component
@EqualsAndHashCode(callSuper = true)
public class NativeHookRecordHistoricalDataJob extends QuartzJobBean {

  @Resource private NativeHookRecordHistoricalDataServiceImpl nativeHookRecordHistoricalDataService;

  @Override
  protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    try {
      log.info("historicalData NativeHookRecordHistoricalDataJob start.");
      nativeHookRecordHistoricalDataService.historicalData("NativeHookRecordHistoricalDataJob");
      log.info("historicalData NativeHookRecordHistoricalDataJob end.");
    } catch (Exception e) {
      log.error("historicalData NativeHookRecordHistoricalDataJob error.", e);
    }
  }
}


/**
 * 这个定时任务用于successDO表6个月的数据保存再successDO表, 6个月外两年零6一个月内的保存在successDO_history表
 * 两年零6一个月外的直接删除
 */
@Slf4j
@Data
@Component
@EqualsAndHashCode(callSuper = true)
public class SuccessHookHistoricalDataJob extends QuartzJobBean {

  @Resource private SuccessHookAddFieldServiceImpl successHookAddFieldService;

  @Override
  protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    log.info("historicalData SuccessHookHistoricalDataJob start.");
    try {
      successHookAddFieldService.historicalData("SuccessHookHistoricalDataJob");
      log.info("historicalData SuccessHookHistoricalDataJob end.");
    } catch (Exception e) {
      log.error("historicalData SuccessHookHistoricalDataJob error.", e);
    }
  }
}

4 优缺点

优点:

  • 提高代码复用性
    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反向控制
    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

5 适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
更多推荐

《Docker 容器化的艺术:深入理解容器技术》

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🐅🐾猫头虎建议程序员必备技术栈一览表📖:🛠️全栈技术FullStack:📚MERN/MEAN/MEVNStack|🌐Jamstack|🌍GraphQL|🔁RESTfulAPI|⚡WebSockets|🔄CI/CD|🌐Git&Versio

Python Functions-函数

目录创建函数调用函数参数还是自变量?参数数量任意参数,*args关键字参数任意关键字参数,**kwargs默认参数值将列表作为参数传递ThepassStatement递归函数是一个只有在被调用时才运行的代码块。可以将称为参数的数据传递到函数中。函数可以作为结果返回数据。创建函数在Python中,函数是使用def关键字定

线程池:神秘的“轻量级线程”

当前我们的多线程部分已经学习了几个代码案例:1.单例模式2.阻塞队列->生产者消费者模型3.定时器4.线程池而线程存在的意义就是,使用进程来实现并发编程会“太重了”,创建和销毁进程都会比较耗资源。但是线程会更加高效。此时,使用多线程就可以在很多时候代替进程来实现并发编程了。但是随着并发程度的提高,随着我们对于性能要求的

【跟小嘉学 Rust 编程】二十九、Rust 中的零拷贝序列化解决方案(rkyv)

系列文章目录【跟小嘉学Rust编程】一、Rust编程基础【跟小嘉学Rust编程】二、Rust包管理工具使用【跟小嘉学Rust编程】三、Rust的基本程序概念【跟小嘉学Rust编程】四、理解Rust的所有权概念【跟小嘉学Rust编程】五、使用结构体关联结构化数据【跟小嘉学Rust编程】六、枚举和模式匹配【跟小嘉学Rust

STM32H5开发(4)----开发板介绍

STM32H5开发----4.开发板介绍套件概述样品申请特征系统控制和生态系统访问功能示意图系统框图跳线设置开发板原理图套件概述STM32H503RBTx_LQFP64是STM32H5系列微控制器的一款出色评估套件,它采用了先进的40nm工艺制造,为开发者提供了卓越的性能和能效。主频高达250MHz的Arm®Corte

华为云CodeArts产品体验的心得体会及想法

文章目录前言CodeArts的产品优势一站式软件开发生产线研发安全Built-In华为多年研发实践能力及规范外溢高质高效敏捷交付功能特性说明体验感受问题描述完结前言华为云作为一家全球领先的云计算服务提供商,致力于为企业和个人用户提供高效、安全、可靠的云服务。在其众多产品中,CodeArts作为一款全新的开发工具集,为开

【STM32】基础知识 第十一课 sys, delay & usart 文件夹

【STM32】基础知识第十一课sys,delay&usart文件夹sys文件介绍delay文件夹函数简介SysTickSysTick工作原理SysTick寄存器介绍delay_init()函数delay_us()函数usart文件夹介绍printf的使用常用输出控制符表常用转椅字符表半主机模式简介sys文件介绍函数分类

苹果从成熟到落地,Apple Newton 背后的工程师们 | 历史上的今天

整理|王启隆透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。1983年3月的最后一个星期日,史蒂夫·乔布斯(SteveJobs)和时任百事公司总裁约翰·斯卡利(JonSculley)坐在阳台上俯瞰纽约中央公园。在深思熟虑之后,斯卡利对着眼前年轻人说:“我们已经互相了解彼此,但是,史蒂夫,我已经考虑过了,我不会

Linux系统使用(超详细)

目录Linux操作系统简介Linux和windows区别Linux常见命令Linux目录结构Linux命令提示符常用命令lscdpwdtouchcatechomkdirrmcpmvvimvim的基本使用grepnetstatLinux面试题Linux操作系统简介Linux操作系统是和windows操作系统是并列的关系。

STM32H5开发(3)----电源控制&RCC

STM32H5开发----3.电源控制&RCCSTM32H503供电样品申请STM32H562/563/573LDO供电STM32H562/563/573SMPS供电LDO/SMPS供电PWR特性电源电压监测温度监测低功耗模式低功耗模式-SLEEP模式低功耗模式-STOP模式低功耗模式-STANDBY模式低功耗模式监控

2023年中职组“网络安全”赛项吉安市竞赛任务书

2023年中职组“网络安全”赛项吉安市竞赛任务书一、竞赛时间总计:360分钟竞赛阶段竞赛阶段任务阶段竞赛任务竞赛时间分值A模块A-1登录安全加固180分钟200分A-2本地安全策略配置A-3流量完整性保护A-4事件监控A-5服务加固A-6防火墙策略B模块B-1Windows操作系统渗透测试400分B-2隐藏信息探索B-

热文推荐