【java设计模式】(10)---模版方法模式(案例解析)
一、概念
1、概念
模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式。
它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
2、举例理解
网上举了一个请客吃饭的例子,我觉得解释的挺好的。我们每个人去请客吃饭。一般都含三个步骤:点单、吃东西、买单,而且顺序就是从做左到右的。
在这三个步骤中,点单和买单大同小异,最大的区别在于第二步——吃什么?吃面条和吃满汉全席可大不相同,如图1所示

所以从开发角度来分析,有时也会遇到类似的情况,某个方法的实现需要多个步骤(类似“ 请客 ”),其中有些步骤是固定的(类似“点单”和“买单”),而有些步骤并不固定,存在
可变性(类似“吃东西”)。
为了提高代码的复用性和系统的灵活性,我们把"点单"和"买单"的实现放在父类中实现,而对于"吃东西",因为差异性就很大所以在父类中只做一个声明,将其具体实现放在不同的
子类中,在一个子类中提供“吃面条”的实现,而另一个子类提供“吃满汉全席”的实现。通过使用模板方法模式,一方面提高了代码的复用性,另一方面还可以利用面向对象的多态性,
在运行时选择一种具体子类,实现完整的“请客”方法,提高系统的灵活性和可扩展性。
3、结构和说明
AbstractClass:抽象类。用来定义算法骨架和原语操作,在这个类里面,还可以提供算法中通用的实现
ConcreteClass:具体实现类。用来实现算法骨架中的某些步骤,完成跟特定子类相关的功能。
public abstract class AbstractClass {
    /**
     * 1、点餐 直接用final修饰,代表子类不能在重写
     */
    private final void order(){
     //点餐
    }
    /**
     * 2、吃东西 吃什么由子类实现
     */
    public abstract void eatSomething();
    /**
     * 3、结算
     */
    private final void settlement(){
    //结算
    }
    /**
     * 记录一次请客
     */
    protected final void workOneDay()
    {
       //点单
        order();
        //吃东西
        eatSomething();
        //结单
        settlement();
    }
   /**
    * 是否需要上厕所,这个其实可以理解成一个钩子,意思就是子类可以选择是否重写,不重写就用父类的方法。
    * 在有些时候 可以通过重写修改boolean的返回值,可以调协一些流程。
    */
    protected boolean isNeedWc()
    {
        return false;
    }
}
子类这里就不写了。
4、优缺点
优点
- 封装不变,扩展可变:父类封装了具体流程以及实现部分不变行为,其它可变行为交由子类进行具体实现;
 - 流程由父类控制,子类进行实现:框架流程由父类限定,子类无法更改;子类可以针对流程某些步骤进行具体实现;
 
缺点
抽象规定了行为,具体负责实现,与通常事物的行为相反,会带来理解上的困难(通俗地说,“父类调用了子类方法”);
5、应用场景
- 多个子类有公有的方法,并且逻辑基本相同时;
 - 重要,复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现;
 - 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类,然后通过钩子函数约束其行为;
 
二、模版方法模式实战示例
之前在参与一个app后段开发,里面有个资讯模块。这个模版的内容不是我们工作人员进行编辑,而是通过爬虫去获取各大网站的资讯,然后处理好后存入我们的数据库。
之前大概爬了十几个网站的资讯。这里的业务流程是这样的 1、爬取资讯内容->2、校验是否已经抓取过->3、存储内容。
这里只有第一步是需要子类去实现的,因为每个网站的数据格式都是不一样的,所以我们需要子类爬取后,统一处理成我们的格式。那么第二步第三步是可以通过父类完成的。
这里代码如下。
1、抽象类
抽象类
/**
 *  定义一个爬取网站资讯的模版
 */
@Slf4j
public abstract class AbstractCrawlNewsService {
   /**
    * 1、爬取各网站消息 由子类去实现
    */
    protected abstract List<Object> crawlPage(int pageNum) throws IOException;
    /**
     * 2、校验该资讯是否已经爬取过 因为这个逻辑是一样的 由父类实现就可以了
     */
    protected final Map<String, Boolean> isCrawled(List<Object> checkParams){
        //数据库校验
        return new HashMap<>();
    }
    /**
     * 3、保存资讯 同样由父类实现即可
     */
    protected final void saveArticle(Object object){
        //保存数据库
    }
    /**
     * 模版方法 定义了上面的一整套流程
     */
    protected void doTask(String url) {
        int pageNum = 1;
        while (true) {
            //1、爬取网站数据,因为网站不可能一次性获取所以资讯数据的 所以进行分页查询 默认第一页开始
            List<Object> newsList = crawlPage(pageNum++);
            // 抓取不到新的内容本次抓取结束
            if (CollectionUtils.isEmpty(newsList)) {
                break;
            }
            //2、校验是否已经抓取过滤(查询我们自己数据库)
            Map<String, Boolean> crawledMap = isCrawled(newsList);
             //3、将数据保存数据库
            for (int i = newsList.size() - 1; i >= 0; i--) {
                Object object = newsList.get(i);
                // 没有爬取过,才进行爬取
                if (!crawledMap.getOrDefault(object.getTitle(), false)) {
                    saveArticle(object);
                }
            }
            //可以考虑请求后休眠以下 因为太频繁IP容易被封。
            ThreadUtils.sleep(2);
        }
    }
}
2、具体实现类
接口
/**
  * @Description: 爬取获悉网接口
  */
public interface CrawlerHuoXingService {
    void start();
}
实现类
/**
 * 抓取火星网新闻
 */
@Slf4j
@Service
public class CrawlerHuoXingServiceImpl extends AbstractCrawlNewsService
        implements CrawlerHuoXingService {
    /**
     * 爬取接口
     */
    @Override
    protected List<Object> crawlPage(int pageNum) throws IOException {
         //通过url获取指定网站接口 进行爬取
        return new ArrayList<>();
    }
    @Override
    public void start() {
        try {
            doTask("http://www.huoxing24.com/news");
        } catch (IOException e) {
            log.error("抓取火星网新闻异常", e);
        }
    }
}
至于什么时候去爬取资讯,我们可以通过定时器,定时去爬取。
定时任务类
/**
  * @Description: 定时爬取火星网资讯
  */
@Slf4j
@Component
public class ScheduleHuoXingTrigger {
    @Autowired
    private CrawlerHuoXingService crawlerHuoXingService;
    /**
     * 定时抓取火星资讯
     */
    @Scheduled(initialDelay = 1000, fixedDelay = 15 * 60 * 1000)
    public void doCrawlHuoXing() {
        try {
            crawlerHuoXingService.start();
        } catch (Exception e) {
            log.error("本次抓取火星资讯异常", e);
        }
    }
}
整个大致流程就是这样,以后要是添加一条资讯那就只要写多个具体实现类就行。
参考
【java设计模式】(10)---模版方法模式(案例解析)的更多相关文章
- JS常用的设计模式(10)——模版方法模式
		
模式方法是预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现.听起来有点像工厂模式( 非前面说过的简单工厂模式 ). 最大的区别是,工厂模式的意图是根据子类的实现最 ...
 - JAVA设计模式之模版方法模式
		
在阎宏博士的<JAVA与模式>一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式 ...
 - java设计模式之模版方法模式以及在java中作用
		
模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有 ...
 - Java设计模式之工厂方法模式(转)  实现是抽象工厂?
		
Java设计模式之工厂方法模式 责任编辑:覃里作者:Java研究组织 2009-02-25 来源:IT168网站 文本Tag: 设计模式 Java [IT168 技术文章] ...
 - Java设计模式系列-工厂方法模式
		
原创文章,转载请标注出处:<Java设计模式系列-工厂方法模式> 一.概述 工厂,就是生产产品的地方. 在Java设计模式中使用工厂的概念,那就是生成对象的地方了. 本来直接就能创建的对象 ...
 - Chapter 10 模版方法模式
		
我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模版模式来处理. 模版方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模 ...
 - 设计模式 笔记 模版方法模式 Template Method
		
//---------------------------15/04/28---------------------------- //TemplateMethod 模版方法模式----类行为型模式 ...
 - 设计模式之模版方法模式(Template Method Pattern)
		
一.什么是模版方法模式? 首先,模版方法模式是用来封装算法骨架的,也就是算法流程 既然被称为模版,那么它肯定允许扩展类套用这个模版,为了应对变化,那么它也一定允许扩展类做一些改变 事实就是这样,模版方 ...
 - Java设计模式 之 工厂方法模式
		
1. 使用设计模式的好处:可提高代码的重复性,让代码更容易被他人理解,保证代码的可靠性. 2. 工厂模式定义:就是创建一个工厂类来创建你需要的类,工厂模式包括工厂模式和抽象工厂模式,抽象工厂模式是工厂 ...
 - java设计模式(二)---工厂方法模式
		
2普通工厂方法模式 就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建. 2.1创建接口 /** * 发送接口 * Created by mrf on 2016/2/25. */ public ...
 
随机推荐
- TP5框架下实现数据库的备份功能-tp5er/tp5-databackup
			
1.安装扩展 方法一: composer require tp5er/tp5-databackup dev-master 方法二 composer.json: "require": ...
 - 美团分布式定时调度框架XXL-Job基本使用
			
一:XXL JOB 基本使用 1.官方中文文档:https://www.xuxueli.com/xxl-job/ 2.基本环境: 2.1:git下载项目, 执行xxl-job数据库初始化脚本 2.2: ...
 - synchronized Lock(本地同步)锁的8种情况
			
Lock(本地同步)锁的8种情况 总结与说明: * 题目: * 1.标准访问,请问是先打印邮件还是短信 Email * 2.email方法新增暂停4秒钟,请问是先打印邮件还是短信 Email * 3. ...
 - Docker系列(19)- 数据卷之Dockerfile
			
初识Dockerfile Dockerfile就是用来构建docker镜像的构建文件!命令脚本! 通过这个脚本生成镜像,镜像是一层一层的,脚本与一个个的命令,每个命令都是一层! # 创建一个docke ...
 - ☕【Java技术指南】「JPA编程专题」让你不再对JPA技术中的“持久化型注解”感到陌生了!
			
JPA的介绍分析 Java持久化API (JPA) 显著简化了Java Bean的持久性并提供了一个对象关系映射方法,该方法使您可以采用声明方式定义如何通过一种标准的可移植方式,将Java 对象映射到 ...
 - Serverless 在 SaaS 领域的最佳实践
			
作者 | 计缘 来源 | Serverless 公众号 随着互联网人口红利逐渐减弱,基于流量的增长已经放缓,互联网行业迫切需要找到一片足以承载自身持续增长的新蓝海,产业互联网正是这一宏大背景下的新趋势 ...
 - 2020.3.21--ICPC训练联盟周赛Benelux Algorithm Programming Contest 2019
			
A Appeal to the Audience 要想使得总和最大,就要使最大值被计算的次数最多.要想某个数被计算的多,就要使得它经过尽量多的节点.于是我们的目标就是找到 k 条从长到短的链,这些链互 ...
 - OpenSSL version mismatch. Built against 1010104f, you have 101000cf
			
现象:公司一台Ubuntu16.04.2的ssh后台无法连接,telnet端口也不通,只能接显示器操作了. 先进行初步排查 查看服务是否启动(公司测试机ssh都是默认启动的) netstat -anp ...
 - C#与java TCP通道加密通信
			
背景说明 公司收费系统需要与银行做实时代收对接,业务协议使用我们收费系统的标准.但是银行要求在业务协议的基础上,使用银行的加密规则. 采用MD5计算报文摘要,保证数据的完整性 采用RSA256对摘要进 ...
 - 合理占用服务器空闲GPU[狗头]
			
合理占用服务器GPU资源[狗头] 场景:当你想进行模型训练时,发现GPU全被占用,怎么办? 解决方案1: 在终端输入如下命令:watch -n 设定刷新时间(s) nvidia-smi 然后记起来了回 ...