写点什么

15. 模版模式设计思想

作者:杨充
  • 2024-11-20
    湖北
  • 本文字数:5810 字

    阅读完需:约 19 分钟

15.模版模式设计思想

目录介绍

  • 01.模版模式基础

  • 1.1 模版模式由来

  • 1.2 模版模式定义

  • 1.3 模版模式场景

  • 1.4 模版模式思考

  • 1.5 模版模式特点

  • 1.6 理解模版唯一性

  • 1.7 主要解决问题

  • 02.模版模式原理

  • 2.1 罗列一个场景

  • 2.2 用例子理解模版

  • 2.3 需求普通实现

  • 2.4 案例演变实现

  • 2.5 模版模式实现步骤

  • 03.模版模式结构

  • 3.1 模版标准案例

  • 3.2 模版模式结构

  • 3.3 模版模式时序图

  • 04.模版模式案例分析

  • 4.1 角色分析

  • 4.2 拓展一个场景

  • 4.3 模版模式实践

  • 05.模版者模式分析

  • 5.1 模版模式优点

  • 5.2 模版模式缺点

  • 5.3 使用建议说明

  • 5.4 思考题考察

  • 06.观察者模式总结

  • 6.1 总结一下学习

  • 6.2 更多内容推荐

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网


https://yccoding.com/


关于设计模式,所有的代码都放到了该项目。设计模式大全

01.模版模式基础介绍

1.0 本博客 AI 摘要

模版模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。这种方式让子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。文章详细介绍了模版模式的基础概念、应用场景、实现原理及优缺点,并通过具体案例深入解析了模版模式的使用方法。适合初学者和有一定经验的开发者深入学习。

1.1 模版模式由来

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。


如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?如何保证架构逻辑的正常执行,而不被子类破坏 ?这个时候可以用模版模式!

1.2 模版模式定义

模板方法模式是类的行为模式。


模板模式(Template)将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。


模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

1.3 模版模式场景

常见模板模式运用场景如下:


  1. Java Servlet:HttpServlet 就是使用了模板模式。HttpServlet 类定义了 service() 方法作为模板方法,子类需要实现 doGet()、doPost() 等方法来处理具体的 HTTP 请求。

  2. Spring Framework:例如,JdbcTemplate 使用了模板模式来简化 JDBC 数据访问代码,定义了模板方法来执行数据库操作,具体的 SQL 语句和参数由子类提供。


总结:当存在一些通用的方法,可以在多个子类中共用时。

1.4 模版模式思考

实际开发中常常会遇到,代码骨架类似甚至相同,只是具体的实现不一样的场景。


例如:休假流程都有开启、编辑、驳回、结束。共享单车都是先开锁、骑行、上锁、付款。每个流程都包含这几个步骤,不同的是不同的流程实例它们的内容不一样。你该如何设计?

1.5 模版模式特点

模板模式的主要特点包括:


  1. 定义一个算法的骨架,将一些步骤留给子类实现。

  2. 允许子类在不改变算法结构的基础上,重新定义某些步骤。

  3. 适用于处理包含一系列基本相同步骤的过程,但某些步骤可能有不同的实现。

1.6 主要解决问题

解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复。

02.模版模式原理

2.1 罗列一个场景

以生活中买菜做饭的例子来写个 Demo,烧饭一般都是买菜、洗菜、烹饪、装盘四大过程。中国自古有八大菜系,制作方式肯定都避不开这四个过程。那在模板模式中如何实现呢?

2.2 用例子理解模版

这些大的步骤固定,不同的是每个实例的具体实现细节不一样。这些类似的业务我们都可以使用模板模式实现。

2.3 需求普通实现

关于实现买菜做饭,做川菜和徽菜。最原始的实现如下所示:


private void cook() {    System.out.println("----------川菜制作------------");    Cooking chuanCaiService = new Cooking(0);    chuanCaiService.process();    System.out.println("-----------徽菜制作-----------");    Cooking huiCaiService = new Cooking(1);    huiCaiService.process();}
public class Cooking {
//type是0表示川菜 //type是1表示徽菜 private int type; public Cooking(int type) { this.type = type; }
public void process() { shopping(); wash(); cooking(); dishedUp(); }
protected void shopping() { if (type == 0) { System.out.println("买菜:黑猪肉一斤,蒜头5个"); } else if (type == 1){ System.out.println("买菜:新鲜鱼一条,红辣椒五两"); }
}
protected void wash() { if (type == 0) { System.out.println("清洗:猪肉洗净,蒜头去皮"); } else if (type == 1){ System.out.println("清洗:红椒洗净切片,鱼头半分"); }
}
protected void cooking() { if (type == 0) { System.out.println("烹饪:大火翻炒,慢火闷油"); } else if (type == 1){ System.out.println("装盘:用长形盘子装盛"); } }
protected void dishedUp() { if (type == 0) { System.out.println("装盘:深碗盛起,热油浇拌"); } else if (type == 1){ System.out.println("烹饪:鱼头水蒸,辣椒过油"); } }}
复制代码


你有没有发现这样写,要是后期在拓展一个鲁菜,粤菜。这样就会修改原代码,会破坏代码的开闭原则。或者我想修改一下不同菜系的做菜步骤,那就导致代码非常难以修改。

2.4 案例演变实现

创建一个抽象类,它的模板方法被设置为 final。为防止恶意操作,一般模板方法都加上 final 关键词。


public abstract class AbstractCookingService {    //买菜    protected abstract void shopping();    //清洗    protected abstract void wash();    //烹饪    protected abstract void cooking();    //装盘    protected abstract void dishedUp();
public final void process() { shopping(); wash(); cooking(); dishedUp(); }}
复制代码


创建实现了上述抽象类的子类。


/** * 徽菜制作大厨 */public class HuiCaiChef extends AbstractCookingService {
@Override protected void shopping() { System.out.println("买菜:新鲜鱼一条,红辣椒五两"); }
@Override protected void wash() { System.out.println("清洗:红椒洗净切片,鱼头半分"); }
@Override protected void cooking() { System.out.println("烹饪:鱼头水蒸,辣椒过油"); }
@Override protected void dishedUp() { System.out.println("装盘:用长形盘子装盛"); }}
/** * 川菜制作大厨 */public class ChuanCaiChef extends AbstractCookingService {
@Override protected void shopping() { System.out.println("买菜:黑猪肉一斤,蒜头5个"); }
@Override protected void wash() { System.out.println("清洗:猪肉洗净,蒜头去皮"); }
@Override protected void cooking() { System.out.println("烹饪:大火翻炒,慢火闷油"); }
@Override protected void dishedUp() { System.out.println("装盘:深碗盛起,热油浇拌"); }}
复制代码


然后测试演示一下


private void test() {    System.out.println("----------川菜制作------------");    AbstractCookingService chuanCaiService = new ChuanCaiChef();    chuanCaiService.process();    System.out.println("-----------徽菜制作-----------");    AbstractCookingService huiCaiService = new HuiCaiChef();    huiCaiService.process();}
复制代码


在模板模式中,定义了一个算法的框架,将算法的具体步骤延迟到子类中实现。这种模式允许子类重写算法的特定步骤而不改变算法的结构。


模板模式中通常包含两个角类,一个模板类和一个具体类,模板类就是一个算法框架。

2.5 模版模式实现步骤

模板模式也没什么高深莫测的,简单来说就是三大步骤:


  1. 创建一个抽象类,定义几个抽象方法和一个 final 修饰的模板方法,而模板方法中设定了抽象方法的执行顺序或逻辑。

  2. 无论子类有多少个,只需要继承该抽象类,实现父类的抽象方法重写自己的业务。

  3. 根据不同的需求创建不同的子类实现,每次调用的地方只需调用模板方法,即可完成特定的模板流程。

03.模版模式结构

3.1 模版标准案例

3.2 模版模式结构

模板模式的主要角色包括:


  1. 抽象类(AbstractClass):负责实现模板方法,并声明在模板方法中所使用的抽象方法。

  2. 具体类(ConcreteClass):实现抽象类中的抽象方法。

  3. 客户端(Client):使用具体类继承的模板方法。

3.3 模版模式时序图

04.模版模式案例分析

4.1 角色分析

抽象类(Abstract):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法,用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。


具体类(Concrete Class):继承抽象类,实现抽象方法。

4.2 拓展一个场景

以订外卖为例,解释一下模板模式。假设订外卖的过程包含三个步骤:选择外卖、支付、取外卖、是否打赏。


我们可以定义一个 OderFood 的抽象类,那么选择外卖就可以是抽象方法,需要子类取实现它,支付和取外卖可以定义为具体方法,另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩。


还需要定义一个模板方法,用以控制流程;不同的商家,如 KFC、星巴克就是具体类。

4.3 模版模式实践

OrderFood——抽象类(Abstract)


/** * @author Created by njy on 2023/6/24 * 1.抽象类(Abstract Class):点外卖 * 包含选择外卖、支付、取外卖三个方法, * 其中选择外卖为抽象方法,需要子类实现 * 支付、取外卖为具体方法 * 另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩 */public abstract class OrderFood {
//模板方法 public void order(){ selectFood(); pay(); getFood(); } //选择外卖 抽象方法 由子类实现具体细节 public abstract void selectFood(); //是否打赏 钩子方法 可以重写来做条件控制 public boolean isGiveAward(){ return false; } //-------具体方法---------- public void pay(){ System.out.println("支付成功,外卖小哥正在快马加鞭~~"); }
//取外卖 public void getFood(){ System.out.println("取到外卖"); if (isGiveAward()){ System.out.println("打赏外卖小哥"); } }}
复制代码


具体类(Concrete Class)


/** * 具体类(Concrete Class):星巴克 */public class Starbucks extends OrderFood{
//实现父类方法 @Override public void selectFood() { System.out.println("一杯抹茶拿铁"); }
//重写钩子方法,打赏外卖小哥 @Override public boolean isGiveAward(){ return true; }}
/** * 具体类(Concrete Class):KFC */public class KFC extends OrderFood{ @Override public void selectFood() { System.out.println("一份汉堡炸鸡四件套"); }}
复制代码


测试一下,代码如下:


//星巴克(重写了钩子方法,打赏外卖小哥)OrderFood orderFood=new Starbucks();orderFood.order();System.out.println("--------KFC------------");//KFCOrderFood orderFood1=new KFC();orderFood1.order();
复制代码

05.模版者模式分析

5.1 模版模式优点

  1. 封装不变部分:算法的不变部分被封装在父类中。

  2. 扩展可变部分:子类可以扩展或修改算法的可变部分。

  3. 提取公共代码:减少代码重复,便于维护。

5.2 模版模式缺点

  1. 类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。

5.3 使用建议说明

  1. 当有多个子类共有的方法且逻辑相同时,考虑使用模板方法模式。

  2. 对于重要或复杂的方法,可以考虑作为模板方法定义在父类中。


注意事项:为了防止恶意修改,模板方法通常使用 final 关键字修饰,避免被子类重写。

5.4 思考题考察

需求 1: 实现字符打印和字符串打印两种不同的打印样式。


分析和实现:定义一个抽象类 AbstractDisplay 其中包含模板方法 display,两个实现模板的具体类,CharDisplay 和 StringDisplay。具体可以看:TemplateDesign


需求 2: 做菜的步骤一般是:洗锅 --> 炒菜 --> 洗碗 ,不同的菜,只是炒菜这一个步骤具体细节是不同的。然后做一个煮糖醋鲤鱼,小炒肉,你该如何实现?


分析和实现:整体流程是一样的,有些步骤一样,有些步骤不一样,但是不使用模板模式,需要每个类都重写一遍方法,这个时候可以用模版模式实现。具体可以看:TemplateDesign

06.观察者模式总结

6.1 总结一下学习

  1. 定义:在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

  2. 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

  3. 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

  4. 何时使用:有一些通用的方法。

  5. 如何解决:将这些通用算法抽象出来。

  6. 关键代码:在抽象类实现,其他步骤在子类实现。

  7. 优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

  8. 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

  9. 使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

6.2 更多内容推荐


23 种设计模式


6.3 更多内容


发布于: 刚刚阅读数: 4
用户头像

杨充

关注

还未添加个人签名 2018-07-30 加入

还未添加个人简介

评论

发布
暂无评论
15.模版模式设计思想_杨充_InfoQ写作社区