【设计模式】【行为型模式】模板方法模式(Template Method)

news/2025/2/13 4:57:46/

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

1.1、什么是模板方法模式

模板模式(Template Method Pattern)是一种行为设计模式,它定义了一个算法的框架,并允许子类在不改变算法结构的情况下重新定义算法的某些步骤。模板模式通过将算法的通用部分放在父类中,而将可变部分留给子类来实现。

1.2、为什么要模板方法模式

1.2.1、代码复用

模板模式的核心是将算法的通用部分放在父类中实现,而将可变的部分留给子类去实现。通过这种方式,模板模式实现了代码复用,同时提供了灵活性。

1.2.2、符合开闭原则

符合开闭原则,开闭原则(Open/Closed Principle)要求软件实体(类、模块、函数等)对扩展开放,对修改关闭。模板模式通过将可变的部分抽象出来,允许子类扩展算法的某些步骤,而无需修改父类的代码。 这符合开闭原则,提高了系统的可维护性和可扩展性。
示例:
在 Java 的 HttpServlet 中,service() 方法定义了处理 HTTP 请求的框架,但具体的 doGet()doPost() 方法由子类实现。如果需要支持新的 HTTP 方法,只需添加新的方法,而无需修改 service() 方法。

1.2.3、支持框架设计

模板模式在框架设计中非常有用。框架通常定义了一个通用的流程,但具体的实现由开发者完成。模板模式允许框架提供默认实现,同时允许开发者自定义某些步骤。
模板模式为框架设计提供了一种灵活且可扩展的方式。
示例:
在 Spring MVC 中,AbstractController 定义了处理 HTTP 请求的框架,但具体的请求处理逻辑由开发者实现。

1.3、怎么实现模板方法模式

定义一个抽象类: 写一个抽象类,里面包含一个模板方法(通常是 final 的),用来定义算法的步骤。在模板方法中,调用一些具体方法(有默认实现)和抽象方法(需要子类实现)。
实现具体类:创建一个子类,继承抽象类。实现抽象类中的抽象方法,提供具体的逻辑。
使用模板模式: 创建具体类的对象,调用模板方法。模板方法会自动按照定义好的步骤执行,同时调用子类实现的逻辑。

【案例】炒菜
炒菜的步骤是固定的,分为倒油、热油、倒菜、倒调料、翻炒等步骤。
在这里插入图片描述
AbstractClass类。这个abstract关键字只能在抽象类中,且子类必须实现abstract修饰的方法,就像2楼是盖在1楼上的。final关键字不是必须加的,看实际业务情况,加上后可以避免子类重写该方法。

java">public abstract class AbstractClass {public final void cookProcess() {//第一步:倒油this.pourOil();//第二步:热油this.heatOil();//第三步:倒蔬菜this.pourVegetable();//第四步:倒调味料this.pourSauce();//第五步:翻炒this.fry();}public void pourOil() {System.out.println("倒油");}//第二步:热油是一样的,所以直接实现public void heatOil() {System.out.println("热油");}//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)public abstract void pourVegetable();//第四步:倒调味料是不一样public abstract void pourSauce();//第五步:翻炒是一样的,所以直接实现public void fry(){System.out.println("炒啊炒啊炒到熟啊");}
}

ConcreteClass_BaoCai 类

java">public class ConcreteClass_BaoCai extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是包菜");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是辣椒");}
}

ConcreteClass_CaiXin类

java">public class ConcreteClass_CaiXin extends AbstractClass {@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是菜心");}@Overridepublic void pourSauce() {System.out.println("下锅的酱料是蒜蓉");}
}
Clinet
public class Client {public static void main(String[] args) {//炒手撕包菜ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();baoCai.cookProcess();//炒蒜蓉菜心ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();caiXin.cookProcess();}
}

二、模板方法模式在源码中运用

2.1、Java集合

AbstractList 是抽象类,提供了列表和集合的基本能力。其中的通用addAll方法,但是具体的实现add方法的实现,交给了子类

java">public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);boolean modified = false;for (E e : c) {add(index++, e);     // 子类来实现modified = true;}return modified;
}public void add(int index, E element) {throw new UnsupportedOperationException();
}

ArrayList的实现

java">public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;
}

LinkedList的实现

java">public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));
}

2.2、AQS

AQS 主要用于实现锁和同步器,它提供了抽象的框架方法,而具体的同步操作实现(如锁的获取、释放)则由子类来实现。这就是模板方法模式的关键特征:定义一个算法的骨架,将一些步骤延迟到子类中。
这里用tryAcquiretryRelease举例

java">protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
}protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();
}

ReentrantLock 中 Sync类实现。ReentrantLock 是 AQS 的一个典型子类实现,它通过内部类 Sync 实现了tryAcquiretryRelease方法。

java">protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取当前同步状态if (c == 0) { // 如果状态为0,表示锁未被占用if (!hasQueuedPredecessors() && // 检查是否有线程在等待compareAndSetState(0, acquires)) { // CAS 设置状态setExclusiveOwnerThread(current); // 设置当前线程为独占线程return true;}}else if (current == getExclusiveOwnerThread()) { // 如果是重入int nextc = c + acquires;if (nextc < 0) // 溢出检查throw new Error("Maximum lock count exceeded");setState(nextc); // 更新状态return true;}return false; // 获取锁失败
}protected final boolean tryRelease(int releases) {int c = getState() - releases; // 计算新的状态if (Thread.currentThread() != getExclusiveOwnerThread()) // 检查当前线程是否持有锁throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 如果状态为0,表示锁完全释放free = true;setExclusiveOwnerThread(null); // 清除独占线程}setState(c); // 更新状态return free;
}

AQS 在acquirerelease方法中会调用子类实现的tryAcquiretryRelease

java">public final void acquire(int arg) {if (!tryAcquire(arg) && // 尝试获取锁acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果失败,加入队列并阻塞selfInterrupt();
}public final boolean release(int arg) {if (tryRelease(arg)) { // 尝试释放锁Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒队列中的下一个线程return true;}return false;
}

三、总结

模板方法的结构:
● 抽象类(Abstract Class):定义一个算法的框架,包含一个模板方法和一些基本操作(可以是抽象的或已经实现的)。
● 模板方法(Template Method):在抽象类中定义的具体方法,它通过调用其他步骤(基本操作)来实现算法的步骤,子类可以通过覆盖某些步骤来调整具体的行为。
● 具体子类(Concrete Class):继承自抽象类,并实现或重写抽象类中的一些方法。

优点:

  1. 代码复用:模板方法通过将公共的算法框架定义在父类中,减少了子类中重复代码的编写。
  2. 扩展性:子类可以根据需要修改或扩展特定的步骤,而无需改变算法的整体结构。
  3. 控制算法流程:通过模板方法,父类可以控制整个算法的执行流程,确保子类在特定的顺序中执行步骤。
  4. 提高一致性:算法的骨架是固定的,这使得不同的子类可以按照相同的步骤进行操作,增强了程序的可维护性。

缺点:

  1. 增加类的数量:每次修改和扩展都可能需要增加新的子类,这可能会使系统类的数量增加,导致复杂度提高。
  2. 不灵活的继承结构:模板方法的设计依赖于继承,如果需要灵活变化或组合不同的步骤,可能会导致类的设计过于僵化。
  3. 难以适应复杂的变化:当算法变得非常复杂或频繁变化时,可能需要频繁修改父类的模板方法,这可能不太符合开闭原则。

适用场景:

  1. 有多个步骤的算法:当一个操作包含多个步骤,而这些步骤可以通过不同的子类进行调整时,模板方法模式非常适用。例如,游戏中不同角色的攻击动作,或者不同文档生成过程的步骤等。
  2. 有部分固定操作和部分可变操作的场景:当某些步骤是固定的,而其他步骤可能会变化时,模板方法模式可以帮助实现固定部分的复用,并允许子类根据需要重写可变部分。
  3. 不希望改变算法结构时:如果你需要确保子类按照某种固定的顺序执行步骤,但又希望在某些步骤上有所不同,模板方法可以很好地适用。

参考

重学 Java 设计模式:实战模板模式「模拟爬虫各类电商商品,生成营销推广海报场景」

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili


http://www.ppmy.cn/news/1571616.html

相关文章

深入探究 Go 语言中的 Fx 框架:依赖注入的强大工具

在软件开发中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是一种重要的设计模式&#xff0c;它可以帮助我们降低代码的耦合度&#xff0c;提高代码的可测试性和可维护性。Go 语言作为一门高效、简洁的编程语言&#xff0c;拥有许多优…

网络编程(udp tcp)

组播通讯&#xff1a; 发送端实现步骤&#xff1a;创建 UDP 类型的套接字设置组播地址和组播端口向组播地址和组播端口发送数据可以接收回复的数据关闭套接字 2.接收端实现步骤&#xff1a; 1.创建UDP类型的套接字 2.绑定任意IP,组播端口到套接字上 3.加入组播组&#xff08;设…

Lisp语言的软件工程

Lisp语言在软件工程中的应用 引言 在计算机科学的发展历程中&#xff0c;编程语言的演变见证了技术的不断进步与理论的深入探索。Lisp&#xff08;LISt Processing&#xff09;作为一种历史悠久的编程语言&#xff0c;自1958年问世以来&#xff0c;已经走过了超过六十年的风雨…

C# OpenCV机器视觉:颜色量化法提取主色

在热闹繁华的广告设计圈里&#xff0c;阿强是个怀揣无限创意&#xff0c;却总被颜色搭配折腾得焦头烂额的设计师。每次接到项目&#xff0c;面对那些色彩斑斓的素材图片&#xff0c;他就像迷失在彩色迷宫里的小可怜。 “哎呀呀&#xff0c;这么多颜色&#xff0c;到底哪个才是…

【论文笔记】Are Self-Attentions Effective for Time Series Forecasting? (NeurIPS 2024)

官方代码https://github.com/dongbeank/CATS Abstract 时间序列预测在多领域极为关键&#xff0c;Transformer 虽推进了该领域发展&#xff0c;但有效性尚存争议&#xff0c;有研究表明简单线性模型有时表现更优。本文聚焦于自注意力机制在时间序列预测中的作用&#xff0c;提…

华硕笔记本怎么一键恢复出厂系统_华硕笔记本一键恢复出厂系统教程

华硕笔记本怎么一键恢复出厂系统&#xff1f; 华硕一键恢复出厂系统是一个安全、高效、方便的恢复方式&#xff0c;让您轻松还原出厂设置&#xff0c;以获得更好的系统性能。如果您的华硕电脑遇到问题&#xff0c;可以使用华硕一键恢复出厂系统功能。下面小编就教大家华硕笔记本…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_show_version_info函数

声明 在 nginx.c 开头 static void ngx_show_version_info(void); 实现 static void ngx_show_version_info(void) {ngx_write_stderr("nginx version: " NGINX_VER_BUILD NGX_LINEFEED);if (ngx_show_help) {ngx_write_stderr("Usage: nginx [-?hvVtTq] [-s s…

Expo运行模拟器失败错误解决(xcrun simctl )

根据你的描述&#xff0c;问题主要涉及两个方面&#xff1a;xcrun simctl 错误和 Expo 依赖版本不兼容。以下是针对这两个问题的解决方案&#xff1a; 解决 xcrun simctl 错误 错误代码 72 通常表明 simctl 工具未正确配置或路径未正确设置。以下是解决步骤&#xff1a; 确保 …