聊一聊 模板方法 在实际场景中的应用

原创 binron 后端元宇宙 2023-05-18 08:20 发表于浙江

收录于合集#设计模式1个

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);
        }
    }
}

整个大致流程就是这样,以后要是添加一条资讯那就只要写多个具体实现类就行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue,插槽的实际应用场景非常广泛。下面列举一些常见的应用场景: 1. 组件间的内容传递:通过插槽,可以在父组件向子组件传递内容,子组件可以接收并渲染这些内容。这样可以使得组件更加灵活和可复用。 2. 组件的布局控制:通过插槽,可以在父组件控制子组件的布局。父组件可以在组件标签内部插入HTML元素或者其他组件,从而实现对子组件的布局定制。 3. 列表渲染:通过插槽,可以在父组件定义列表项的模板,并将数据传递给子组件进行渲染。这样可以实现灵活的列表渲染,并且每个列表项的模板可以根据需求进行定制。 4. 多个插槽的使用:在一个组件可以定义多个插槽,通过插槽名来区分不同的插槽。这样可以在父组件根据需要插入不同的内容,并在子组件将这些内容进行渲染。 综上所述,Vue的插槽可以用于组件间的内容传递、布局控制、列表渲染以及多个插槽的使用等场景,使得组件的灵活性和复用性大大提高。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [VUE插槽slot用法 及其使用场景](https://blog.csdn.net/xifanxiaochaorou/article/details/123554221)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值