系列文章目录
第一章 解锁单例模式:Java世界的唯一实例之道
第二章 解锁工厂模式:工厂模式探秘
第三章 解锁代理模式:代理模式的多面解析与实战
第四章 解锁装饰器模式:代码增强的魔法宝典
第五章 解锁建造者模式:Java 编程中的对象构建秘籍
第六章 解锁原型模式:Java 中的高效对象创建之道
第七章 解锁适配器模式:代码重构与架构优化的魔法钥匙
第八章 解锁桥接模式:Java架构中的解耦神器
第九章 解锁组合模式:Java 代码中的树形结构奥秘
第十章 解锁享元模式:内存优化与性能提升的关键密码
第十一章 解锁外观模式:Java 编程中的优雅架构之道
第十二章 解锁观察者模式:Java编程中的高效事件管理之道
第十三章 解锁策略模式:Java 实战与应用全景解析
第十四章 解锁状态模式:Java 编程中的行为魔法
第十五章 解锁模板方法模式:Java 实战与应用探秘
第十六章 解锁命令模式:Java 编程中的解耦神器
第十七章 解锁迭代器模式:Java 编程的遍历神器
第十八章 解锁责任链模式:Java 实战与应用探秘
第十九章 解锁中介者模式:代码世界的“社交达人”
第二十章 解锁备忘录模式:代码世界的时光机
第二十一章 解锁访问者模式:Java编程的灵活之道
第二十二章 解锁Java解释器模式:概念、应用与实战
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现 。这样一来,子类可以在不改变算法结构的情况下,重新定义该算法的某些特定步骤。这种模式就像是为算法搭建了一个 “模板”,固定了整体的流程框架,而把变化的部分留给子类去填充。
我们以做菜为例来理解。做菜通常有一个大致固定的流程:准备食材、烹饪、调味、装盘 。这一系列步骤构成了做菜的算法骨架。但是,不同菜品在这些步骤中的具体实现却大不相同。比如做红烧肉,准备食材时需要五花肉、葱姜蒜等;烹饪时要先煸炒五花肉出油,再加入调料炖煮;调味时需要酱油、冰糖等。而做清炒时蔬,准备食材就是各种蔬菜;烹饪时快速翻炒;调味简单放点盐和鸡精即可。
在这个例子中,做菜的整体流程就是模板方法模式中的算法骨架,由一个抽象类来定义,比如可以定义一个AbstractCooking抽象类。而具体每道菜的不同做法,像红烧肉和清炒时蔬的做法,就是由子类来实现的,分别对应BraisedPork类和StirFriedVegetables类。
AbstractCooking类中定义的模板方法(template method)会按照固定顺序调用各个步骤的方法,而这些步骤方法中的一部分(如烹饪和调味的具体操作)是抽象方法,需要子类去实现。这样,通过模板方法模式,我们既保证了做菜流程的一致性,又能根据不同菜品实现个性化的烹饪。
模板方法模式主要包含两个核心角色:抽象类(Abstract Class)和具体子类(Concrete Class)。
抽象类:它定义了算法的骨架,也就是模板方法。模板方法中包含了一系列按照特定顺序调用的基本方法,这些基本方法可以是抽象方法、具体方法或者钩子方法。抽象方法需要子类去实现,它代表了算法中变化的部分;具体方法是已经在抽象类中实现好的,子类可以直接继承使用;钩子方法则是一种特殊的方法,它在抽象类中有默认实现,子类可以根据需要选择性地重写,用于控制算法的某些特定行为。
具体子类:实现抽象类中定义的抽象方法,从而完成算法中特定子类的步骤。每个具体子类对应算法的一种具体实现,它们通过实现抽象方法来定制算法的行为。
用 UML 类图来展示模板方法模式的结构,会更加清晰直观。如下:
在这个类图中,AbstractClass是抽象类,它定义了templateMethod模板方法,以及primitiveOperation1和primitiveOperation2两个抽象方法。ConcreteClassA和ConcreteClassB是具体子类,它们继承自AbstractClass,并实现了其中的抽象方法。
以考试试卷类为例进一步说明。假设我们有一个考试系统,不同科目的试卷有一些共同的流程,如填写考生信息、答题、交卷 ,但具体的题目内容和评分标准因科目而异。我们可以定义一个抽象的AbstractExamPaper类作为抽象类:
public abstract class AbstractExamPaper {
// 模板方法,定义考试流程
public final void takeExam() {
fillStudentInfo();
answerQuestions();
submitPaper();
}
// 具体方法,填写考生信息
protected void fillStudentInfo() {
System.out.println("填写考生姓名、学号等信息");
}
// 抽象方法,答题,由子类实现
protected abstract void answerQuestions();
// 具体方法,交卷
protected void submitPaper() {
System.out.println("交卷");
}
}
然后,针对不同科目创建具体子类,比如MathExamPaper类:
public class MathExamPaper extends AbstractExamPaper {
@Override
protected void answerQuestions() {
System.out.println("回答数学题目,如1 + 1 = 2");
}
}
在这个例子中,AbstractExamPaper类的takeExam方法就是模板方法,它定义了考试的整体流程。fillStudentInfo和submitPaper是具体方法,answerQuestions是抽象方法,由MathExamPaper子类实现。通过这种方式,我们可以轻松创建不同科目的试卷类,它们共享相同的考试流程框架,同时又能根据科目特点定制题目内容。
模板方法模式与策略模式、工厂方法模式、装饰器模式、观察者模式等其他设计模式在原理、实现方式和应用场景上存在明显差异。
与策略模式相比:
与工厂方法模式相比:
与装饰器模式相比:
与观察者模式相比:
我们以制作咖啡和茶的过程为例,来深入理解模板方法模式在 Java 代码中的实现。首先,定义一个抽象类CaffeineBeverage,它包含了制作饮料的模板方法prepareRecipe,以及一些抽象方法和具体方法。代码如下:
public abstract class CaffeineBeverage {
// 模板方法,定义制作饮料的整体流程
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 具体方法,煮水
void boilWater() {
System.out.println("Boiling water");
}
// 抽象方法,冲泡,由子类实现
abstract void brew();
// 具体方法,倒入杯中
void pourInCup() {
System.out.println("Pouring into cup");
}
// 抽象方法,添加调料,由子类实现
abstract void addCondiments();
}
在这个抽象类中,prepareRecipe方法就是模板方法,它定义了制作饮料的固定流程:先煮水,然后冲泡,接着倒入杯中,最后添加调料。boilWater和pourInCup是具体方法,它们的实现是固定的,不需要子类去改变。而brew和addCondiments是抽象方法,需要具体的子类来实现,因为不同的饮料(如咖啡和茶)在冲泡和添加调料的方式上是不同的。
接下来,我们创建两个具体的子类Coffee和Tea,分别实现CaffeineBeverage抽象类中的抽象方法。
public class Coffee extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
public class Tea extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding Lemon");
}
}
在Coffee类中,brew方法实现为通过过滤器滴滤咖啡,addCondiments方法实现为添加糖和牛奶。在Tea类中,brew方法实现为浸泡茶叶,addCondiments方法实现为添加柠檬。
最后,我们通过客户端代码来测试模板方法模式的运行效果。
public class BeverageTest {
public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("-------------------");
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
}
}
在客户端代码中,我们分别创建了Coffee和Tea的实例,并调用它们的prepareRecipe方法。由于prepareRecipe方法是在抽象类CaffeineBeverage中定义的模板方法,它会按照固定的流程调用各个步骤的方法,而具体的冲泡和添加调料步骤则会根据对象的实际类型(Coffee或Tea)来调用相应子类中实现的方法。
运行上述客户端代码,输出结果如下:
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and Milk
-------------------
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon
从输出结果可以清晰地看到,制作咖啡和茶的过程遵循了相同的模板方法流程,但在冲泡和添加调料这两个步骤上展现出了各自的特性,这正是模板方法模式的魅力所在。它通过将通用的算法结构定义在抽象类中,把变化的部分留给子类实现,既保证了代码的复用性,又提供了灵活的扩展性。
在上述模板方法模式的 Java 代码实现中,有几个关键的结构和要点需要深入理解。
首先,抽象类CaffeineBeverage中的模板方法prepareRecipe被声明为final。这是一个非常重要的设计决策,final修饰符确保了这个方法不能被子类重写 。因为模板方法定义了整个算法的骨架和执行顺序,它是整个模式的核心流程,一旦被子类重写,就可能破坏掉原本设计好的算法结构,导致整个流程的混乱。例如,如果Coffee或Tea子类重写了prepareRecipe方法,那么就可能不再按照先煮水、再冲泡、然后倒入杯中、最后添加调料的顺序来制作饮料,这显然不符合我们的设计初衷。
其次,抽象类中的抽象方法,如brew和addCondiments,通常声明为protected。这是因为这些方法主要是为了让子类去实现,对外部客户端来说,它们并不需要直接访问这些方法。protected访问修饰符保证了这些抽象方法只能在子类中被访问和实现,同时又避免了它们被外部类随意调用,从而保证了代码的安全性和封装性。比如,如果将brew方法声明为public,那么外部类就可以直接调用这个方法,这可能会导致外部类绕过模板方法的整体流程,单独执行某个步骤,破坏了整个算法的完整性。
另外,具体子类Coffee和Tea通过重写抽象类中的抽象方法,实现了个性化的功能。这是模板方法模式实现灵活性的关键所在。每个具体子类根据自身的特点,提供了不同的实现方式。例如,Coffee类中对brew方法的实现是通过过滤器滴滤咖啡,而Tea类中对brew方法的实现是浸泡茶叶。这种方式使得在不改变模板方法整体结构的情况下,能够轻松实现不同的业务逻辑。
在实际应用中,我们可以对模板方法模式的代码进行进一步优化,以满足更复杂的业务需求。其中一种常见的优化方式是引入钩子方法(Hook Method) 。钩子方法是一种特殊的方法,它在抽象类中通常有一个默认的实现,子类可以根据需要选择性地重写它,从而控制算法的某些特定行为。
我们还是以上述制作饮料的例子来进行说明。假设我们希望在制作饮料时,能够根据用户的选择来决定是否添加调料。我们可以在抽象类CaffeineBeverage中添加一个钩子方法customerWantsCondiments,代码如下:
public abstract class CaffeineBeverage {
// 模板方法,定义制作饮料的整体流程
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
// 具体方法,煮水
void boilWater() {
System.out.println("Boiling water");
}
// 抽象方法,冲泡,由子类实现
abstract void brew();
// 具体方法,倒入杯中
void pourInCup() {
System.out.println("Pouring into cup");
}
// 抽象方法,添加调料,由子类实现
abstract void addCondiments();
// 钩子方法,默认返回true,表示默认添加调料
boolean customerWantsCondiments() {
return true;
}
}
在这个优化后的代码中,prepareRecipe模板方法中增加了一个条件判断,只有当customerWantsCondiments方法返回true时,才会执行添加调料的步骤。而customerWantsCondiments方法在抽象类中提供了一个默认的实现,返回true,即默认添加调料。
然后,在具体子类中,我们可以根据需要重写这个钩子方法。例如,对于Coffee类,如果我们希望默认不添加调料,可以这样实现:
public class Coffee extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
// 重写钩子方法,返回false,表示不添加调料
@Override
boolean customerWantsCondiments() {
return false;
}
}
这样,当使用Coffee类制作咖啡时,默认就不会添加调料,除非在Coffee类中再次重写customerWantsCondiments方法返回true。
再以一个课程创建流程为例,假设我们有一个抽象的CourseCreation类,定义了创建课程的模板方法:
public abstract class CourseCreation {
// 模板方法,定义创建课程的流程
public final void createCourse() {
checkPermissions();
prepareMaterials();
if (isAdvancedCourse()) {
conductAdvancedReview();
}
publishCourse();
}
// 具体方法,检查权限
protected void checkPermissions() {
System.out.println("检查用户权限,确认是否有权限创建课程");
}
// 抽象方法,准备课程材料
protected abstract void prepareMaterials();
// 具体方法,发布课程
protected void publishCourse() {
System.out.println("课程创建完成,发布课程");
}
// 钩子方法,判断是否是高级课程,默认返回false
protected boolean isAdvancedCourse() {
return false;
}
// 抽象方法,高级课程审核
protected abstract void conductAdvancedReview();
}
在这个例子中,createCourse是模板方法,定义了创建课程的基本流程。checkPermissions和publishCourse是具体方法,prepareMaterials和conductAdvancedReview是抽象方法,需要子类实现。isAdvancedCourse是钩子方法,默认返回false,表示普通课程创建流程。如果是创建高级课程的子类,可以重写isAdvancedCourse方法返回true,从而执行高级课程审核步骤。
public class AdvancedCourseCreation extends CourseCreation {
@Override
protected void prepareMaterials() {
System.out.println("准备高级课程材料,如深度技术文档、专家讲座视频等");
}
@Override
protected void conductAdvancedReview() {
System.out.println("邀请专家进行高级课程审核,评估课程内容深度和专业性");
}
// 重写钩子方法,返回true,表示是高级课程
@Override
protected boolean isAdvancedCourse() {
return true;
}
}
通过引入钩子方法,我们可以根据不同的业务场景和需求,灵活地控制算法的执行流程,使得模板方法模式在实际应用中更加健壮和灵活。
在软件开发中,框架开发是模板方法模式的重要应用领域。以 Spring 框架中的 JdbcTemplate 为例,它极大地简化了 JDBC 操作,其内部就巧妙地运用了模板方法模式。
JdbcTemplate 的主要作用是封装了与数据库交互的通用操作,如获取数据库连接、创建语句、处理结果集以及异常处理等。这些通用操作构成了一个模板方法,而具体的 SQL 执行逻辑则被延迟到子类或回调函数中实现。这样,开发者在使用 JdbcTemplate 时,只需关注业务相关的 SQL 语句,而无需重复编写繁琐的数据库连接和操作的基础代码。
下面通过一段简化的代码示例来深入理解 JdbcTemplate 中模板方法模式的实现机制:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcTemplateExample {
public static void main(String[] args) {
// 配置数据源
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/yourdatabase");
dataSource.setUsername("yourusername");
dataSource.setPassword("yourpassword");
// 创建JdbcTemplate实例
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 定义SQL查询语句
String sql = "SELECT id, name, age FROM users WHERE age >?";
// 定义RowMapper,用于将ResultSet映射为Java对象
RowMapper<User> rowMapper = new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
}
};
// 执行查询操作,使用模板方法模式
List<User> users = jdbcTemplate.query(sql, rowMapper, 20);
// 输出查询结果
for (User user : users) {
System.out.println(user);
}
}
}
class User {
private int id;
private String name;
private int age;
// Getter和Setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
在上述代码中,JdbcTemplate 的query方法就是模板方法,它定义了执行 SQL 查询的通用流程:
而具体的 SQL 语句(如SELECT id, name, age FROM users WHERE age >?)以及RowMapper的实现(将ResultSet映射为User对象)则是由开发者根据业务需求来提供的,这部分就是模板方法中延迟到子类或回调函数实现的部分。
通过这种方式,JdbcTemplate 实现了代码的高度复用和灵活性。不同的数据库操作(如查询、插入、更新、删除)都可以通过调用相应的模板方法,并传入不同的 SQL 语句和参数来完成,避免了大量重复代码的编写。同时,开发者可以专注于业务逻辑的实现,提高了开发效率和代码的可维护性。
在业务逻辑处理中,模板方法模式同样发挥着重要作用。以电商系统的订单处理流程为例,它涉及多个复杂的步骤,如订单创建、支付处理、库存检查、发货处理以及订单状态更新等。虽然不同类型的订单(如普通商品订单、预售商品订单、团购订单等)在某些处理步骤上存在差异,但整体的处理流程是相似的。这时,我们就可以运用模板方法模式来设计订单处理的逻辑。
首先,定义一个抽象的OrderProcessor类,它包含了订单处理的模板方法和一些抽象方法。模板方法定义了订单处理的整体流程,而抽象方法则由具体的订单子类来实现,以处理不同订单类型的特殊逻辑。代码示例如下:
public abstract class OrderProcessor {
// 模板方法,定义订单处理的整体流程
public final void processOrder() {
createOrder();
if (isPaymentRequired()) {
processPayment();
}
checkStock();
shipOrder();
updateOrderStatus();
}
// 抽象方法,创建订单,由子类实现
protected abstract void createOrder();
// 钩子方法,判断是否需要支付,默认需要支付
protected boolean isPaymentRequired() {
return true;
}
// 抽象方法,处理支付,由子类实现
protected abstract void processPayment();
// 具体方法,检查库存
protected void checkStock() {
System.out.println("检查商品库存");
}
// 抽象方法,发货,由子类实现
protected abstract void shipOrder();
// 具体方法,更新订单状态
protected void updateOrderStatus() {
System.out.println("更新订单状态为已完成");
}
}
然后,针对不同类型的订单,创建具体的子类,如NormalOrderProcessor类用于处理普通商品订单:
public class NormalOrderProcessor extends OrderProcessor {
@Override
protected void createOrder() {
System.out.println("创建普通商品订单");
}
@Override
protected void processPayment() {
System.out.println("处理普通商品订单的支付,如使用支付宝支付");
}
@Override
protected void shipOrder() {
System.out.println("发货普通商品订单,使用快递发货");
}
}
再比如,PreSaleOrderProcessor类用于处理预售商品订单,预售商品订单可能在支付方式和发货时间上与普通订单不同:
public class PreSaleOrderProcessor extends OrderProcessor {
@Override
protected void createOrder() {
System.out.println("创建预售商品订单");
}
@Override
protected boolean isPaymentRequired() {
// 假设预售商品订单先支付定金,这里简单示例返回true表示需要支付
return true;
}
@Override
protected void processPayment() {
System.out.println("处理预售商品订单的支付,如支付定金");
}
@Override
protected void shipOrder() {
System.out.println("预售商品订单在规定时间发货");
}
}
在客户端代码中,我们可以这样使用:
public class OrderSystem {
public static void main(String[] args) {
OrderProcessor normalOrder = new NormalOrderProcessor();
normalOrder.processOrder();
System.out.println("-------------------");
OrderProcessor preSaleOrder = new PreSaleOrderProcessor();
preSaleOrder.processOrder();
}
}
在上述代码中,OrderProcessor类的processOrder方法是模板方法,它定义了订单处理的标准流程:创建订单、处理支付(如果需要)、检查库存、发货以及更新订单状态。NormalOrderProcessor和PreSaleOrderProcessor子类继承自OrderProcessor,并实现了其中的抽象方法,以满足各自订单类型的特殊需求。
通过使用模板方法模式,电商系统的订单处理逻辑变得更加清晰和易于维护。当有新的订单类型加入时,只需要创建一个新的子类并实现相应的抽象方法,而不需要修改现有的订单处理流程代码,符合开闭原则。同时,公共的订单处理逻辑被封装在抽象类中,提高了代码的复用性,减少了重复代码的编写。
在日常生活的烹饪场景中,模板方法模式有着生动的体现。以炒菜为例,虽然不同的菜品在食材和调料的选择上千差万别,但炒菜的基本流程是相对固定的。这个固定的流程就可以看作是模板方法模式中的模板方法,而具体食材和调料的添加则是由不同菜品对应的子类来实现的变化步骤。
假设我们要定义一个炒菜的模板,首先创建一个抽象类AbstractCooking:
public abstract class AbstractCooking {
// 模板方法,定义炒菜的整体流程
public final void cook() {
washPot();
heatPot();
pourOil();
addIngredients();
stirFry();
addSeasonings();
serve();
}
// 具体方法,洗锅
protected void washPot() {
System.out.println("洗锅");
}
// 具体方法,热锅
protected void heatPot() {
System.out.println("热锅");
}
// 具体方法,倒油
protected void pourOil() {
System.out.println("倒入食用油");
}
// 抽象方法,添加食材,由子类实现
protected abstract void addIngredients();
// 具体方法,翻炒
protected void stirFry() {
System.out.println("翻炒食材");
}
// 抽象方法,添加调料,由子类实现
protected abstract void addSeasonings();
// 具体方法,装盘
protected void serve() {
System.out.println("将炒好的菜装盘");
}
}
接下来,我们创建两个具体的子类,StirFriedTomatoAndEgg类用于炒西红柿鸡蛋:
public class StirFriedTomatoAndEgg extends AbstractCooking {
@Override
protected void addIngredients() {
System.out.println("加入西红柿和鸡蛋");
}
@Override
protected void addSeasonings() {
System.out.println("加入盐和糖调味");
}
}
StirFriedGreenBeans类用于炒四季豆:
public class StirFriedGreenBeans extends AbstractCooking {
@Override
protected void addIngredients() {
System.out.println("加入四季豆");
}
@Override
protected void addSeasonings() {
System.out.println("加入盐、生抽和蒜末调味");
}
}
在客户端代码中,我们可以这样调用:
public class CookingTest {
public static void main(String[] args) {
AbstractCooking tomatoAndEgg = new StirFriedTomatoAndEgg();
tomatoAndEgg.cook();
System.out.println("-------------------");
AbstractCooking greenBeans = new StirFriedGreenBeans();
greenBeans.cook();
}
}
在这个例子中,AbstractCooking类的cook方法是模板方法,它定义了炒菜的标准流程:洗锅、热锅、倒油、添加食材、翻炒、添加调料、装盘。StirFriedTomatoAndEgg和StirFriedGreenBeans子类继承自AbstractCooking,并实现了addIngredients和addSeasonings抽象方法,以适应各自菜品的特点。
通过这种方式,我们可以清晰地看到模板方法模式在烹饪场景中的应用。它不仅让我们对炒菜的流程有了一个清晰的框架,而且当我们想要尝试新的菜品时,只需要创建一个新的子类并实现相应的食材和调料添加方法,而不需要重新定义整个炒菜流程,体现了模板方法模式的灵活性和复用性。
在工作场景中,模板方法模式也有着广泛的应用。以员工入职流程为例,无论新员工入职到哪个部门,都有一些基本的固定步骤,如填写入职登记表、复印相关证件、签订劳动合同等。这些固定步骤可以看作是模板方法模式中的模板方法,而不同部门可能会有不同的入职培训内容和岗位介绍等,这些差异部分则由具体的部门子类来实现。
我们先定义一个抽象的EmployeeOnboarding类:
public abstract class EmployeeOnboarding {
// 模板方法,定义员工入职的整体流程
public final void onboard() {
fillOnboardingForm();
copyDocuments();
signContract();
departmentSpecificOnboarding();
}
// 具体方法,填写入职登记表
protected void fillOnboardingForm() {
System.out.println("新员工填写入职登记表");
}
// 具体方法,复印证件
protected void copyDocuments() {
System.out.println("复印新员工的身份证、学历证书等证件");
}
// 具体方法,签订劳动合同
protected void signContract() {
System.out.println("新员工签订劳动合同");
}
// 抽象方法,部门特定的入职步骤,由子类实现
protected abstract void departmentSpecificOnboarding();
}
然后,创建TechnicalDepartmentOnboarding类用于技术部门的新员工入职:
public class TechnicalDepartmentOnboarding extends EmployeeOnboarding {
@Override
protected void departmentSpecificOnboarding() {
System.out.println("技术部门进行技术工具使用培训和项目介绍");
}
}
再创建SalesDepartmentOnboarding类用于销售部门的新员工入职:
public class SalesDepartmentOnboarding extends EmployeeOnboarding {
@Override
protected void departmentSpecificOnboarding() {
System.out.println("销售部门进行产品销售技巧培训和客户资源介绍");
}
}
在客户端代码中,我们可以这样使用:
public class OnboardingSystem {
public static void main(String[] args) {
EmployeeOnboarding technicalOnboarding = new TechnicalDepartmentOnboarding();
technicalOnboarding.onboard();
System.out.println("-------------------");
EmployeeOnboarding salesOnboarding = new SalesDepartmentOnboarding();
salesOnboarding.onboard();
}
}
在上述代码中,EmployeeOnboarding类的onboard方法是模板方法,它定义了员工入职的基本流程:填写入职登记表、复印证件、签订劳动合同以及部门特定的入职步骤。TechnicalDepartmentOnboarding和SalesDepartmentOnboarding子类继承自EmployeeOnboarding,并实现了departmentSpecificOnboarding抽象方法,以满足各自部门的特殊入职需求。
通过模板方法模式在员工入职流程中的应用,公司可以确保新员工入职流程的规范性和一致性,同时又能根据不同部门的特点进行个性化的入职安排。当有新的部门加入或者部门入职流程发生变化时,只需要修改或创建相应的子类,而不会影响到整体的入职流程框架,提高了工作流程的可维护性和扩展性。
模板方法模式的一个显著优点是提高了代码的复用性。通过将算法中通用的部分抽象到抽象类中,子类只需实现那些特定的步骤,避免了在不同子类中重复编写相同的代码。
以汽车制造为例,不同品牌和型号的汽车在制造过程中都有一些共同的步骤,如车身组装、发动机安装、内饰装配等。我们可以定义一个抽象的AbstractCarManufacturing类,将这些共同步骤封装在模板方法中:
public abstract class AbstractCarManufacturing {
// 模板方法,定义汽车制造的整体流程
public final void manufactureCar() {
assembleBody();
installEngine();
installInterior();
qualityInspection();
}
// 具体方法,车身组装
protected void assembleBody() {
System.out.println("进行车身组装");
}
// 具体方法,发动机安装
protected void installEngine() {
System.out.println("安装发动机");
}
// 抽象方法,内饰装配,由子类实现
protected abstract void installInterior();
// 具体方法,质量检测
protected void qualityInspection() {
System.out.println("进行质量检测");
}
}
然后,不同品牌的汽车制造子类可以继承这个抽象类,并实现installInterior抽象方法,以满足各自品牌的内饰装配需求。例如,BMWCarManufacturing类:
public class BMWCarManufacturing extends AbstractCarManufacturing {
@Override
protected void installInterior() {
System.out.println("安装宝马品牌的豪华内饰,如真皮座椅、高级音响等");
}
}
再比如,ToyotaCarManufacturing类:
public class ToyotaCarManufacturing extends AbstractCarManufacturing {
@Override
protected void installInterior() {
System.out.println("安装丰田品牌的实用内饰,注重舒适性和经济性");
}
}
在这个例子中,AbstractCarManufacturing类中的manufactureCar模板方法定义了汽车制造的整体流程,assembleBody、installEngine和qualityInspection方法是所有汽车制造过程中通用的步骤,被封装在抽象类中,实现了代码的复用。不同品牌的汽车制造子类只需专注于实现各自独特的内饰装配步骤,减少了重复代码的编写,提高了开发效率。
模板方法模式使得代码的可维护性得到了显著增强。由于算法的核心骨架和通用部分都集中在抽象类中,当需要修改算法的某些通用步骤或者调整算法结构时,只需要在抽象类中进行修改,而不会影响到各个具体子类。这大大降低了代码维护的难度和成本,提高了代码的稳定性。
以电商系统的订单处理流程为例,假设最初的订单处理流程中,支付处理步骤是通过调用第三方支付接口来实现的,代码如下:
public abstract class OrderProcessor {
// 模板方法,定义订单处理的整体流程
public final void processOrder() {
createOrder();
processPayment();
checkStock();
shipOrder();
updateOrderStatus();
}
// 抽象方法,创建订单,由子类实现
protected abstract void createOrder();
// 具体方法,处理支付
protected void processPayment() {
System.out.println("调用第三方支付接口进行支付处理");
}
// 具体方法,检查库存
protected void checkStock() {
System.out.println("检查商品库存");
}
// 抽象方法,发货,由子类实现
protected abstract void shipOrder();
// 具体方法,更新订单状态
protected void updateOrderStatus() {
System.out.println("更新订单状态为已完成");
}
}
随着业务的发展,电商系统决定将支付处理方式改为内部自研的支付系统。此时,只需要在抽象类OrderProcessor中修改processPayment方法的实现,而不需要修改各个具体的订单处理子类,如下:
public abstract class OrderProcessor {
// 模板方法,定义订单处理的整体流程
public final void processOrder() {
createOrder();
processPayment();
checkStock();
shipOrder();
updateOrderStatus();
}
// 抽象方法,创建订单,由子类实现
protected abstract void createOrder();
// 具体方法,处理支付,修改为内部自研支付系统
protected void processPayment() {
System.out.println("使用内部自研支付系统进行支付处理");
}
// 具体方法,检查库存
protected void checkStock() {
System.out.println("检查商品库存");
}
// 抽象方法,发货,由子类实现
protected abstract void shipOrder();
// 具体方法,更新订单状态
protected void updateOrderStatus() {
System.out.println("更新订单状态为已完成");
}
}
模板方法模式非常便于功能扩展。当有新的业务需求或者需要增加新的功能时,我们只需要创建一个新的子类,继承抽象类,并实现其中的抽象方法,就可以轻松地扩展新的功能,而不需要修改现有的代码。这种方式符合开闭原则,即对扩展开放,对修改关闭。
以课程创建流程为例,假设最初我们只有普通课程的创建流程,通过一个抽象类CourseCreation来定义模板方法:
public abstract class CourseCreation {
// 模板方法,定义创建课程的流程
public final void createCourse() {
checkPermissions();
prepareMaterials();
publishCourse();
}
// 具体方法,检查权限
protected void checkPermissions() {
System.out.println("检查用户权限,确认是否有权限创建课程");
}
// 抽象方法,准备课程材料
protected abstract void prepareMaterials();
// 具体方法,发布课程
protected void publishCourse() {
System.out.println("课程创建完成,发布课程");
}
}
现在,业务需求发生变化,需要增加一种新的课程类型 —— 在线直播课程,并且在线直播课程在创建过程中需要额外进行直播设备调试和直播时间安排。我们可以创建一个新的子类LiveCourseCreation来实现这些新的功能:
public class LiveCourseCreation extends CourseCreation {
@Override
protected void prepareMaterials() {
System.out.println("准备在线直播课程材料,如直播课件、直播脚本等");
debugLiveEquipment();
arrangeLiveTime();
}
// 新增方法,调试直播设备
private void debugLiveEquipment() {
System.out.println("调试直播设备,确保设备正常运行");
}
// 新增方法,安排直播时间
private void arrangeLiveTime() {
System.out.println("安排在线直播课程的时间,确定直播开始和结束时间");
}
}
在这个例子中,通过创建新的子类LiveCourseCreation,我们成功地扩展了新的课程类型的创建功能,而没有对原有的CourseCreation抽象类和其他普通课程创建子类进行任何修改。这种方式使得系统具有良好的扩展性,能够轻松应对不断变化的业务需求。
模板方法模式的一个明显缺点是子类的灵活性受到一定限制。由于算法的骨架已经在抽象类中固定下来,子类只能在抽象类规定的框架内实现具体的步骤,无法改变算法的整体结构。这意味着如果某个子类需要一种与现有算法结构完全不同的实现方式,使用模板方法模式可能就不太合适。
例如,在一个游戏开发项目中,定义了一个抽象类AbstractGame来表示游戏的基本流程,其中包括游戏初始化、游戏运行、游戏结束等步骤,这些步骤构成了游戏的算法骨架。
public abstract class AbstractGame {
// 模板方法,定义游戏流程
public final void playGame() {
initializeGame();
runGame();
endGame();
}
// 抽象方法,游戏初始化,由子类实现
protected abstract void initializeGame();
// 抽象方法,游戏运行,由子类实现
protected abstract void runGame();
// 抽象方法,游戏结束,由子类实现
protected abstract void endGame();
}
现在,有一个特殊的游戏类型,它的游戏流程与其他游戏有很大的不同,需要在游戏运行之前进行一段剧情引导,并且游戏结束后不是直接结束,而是根据玩家的表现进入不同的结局分支。由于模板方法模式已经固定了游戏流程的骨架,这个特殊游戏类型的子类无法直接通过继承AbstractGame来实现这种独特的流程,因为它需要改变算法的整体结构,而模板方法模式不允许子类这样做。这就限制了子类的灵活性,使得模板方法模式在某些特殊场景下的应用受到了一定的局限。
模板方法模式可能会导致代码的可读性下降。因为算法的实现被分散在抽象类和多个具体子类中,阅读代码时需要在不同的类和方法之间来回切换,才能完整地理解整个算法的执行过程。特别是当算法比较复杂,涉及多个抽象方法和具体方法的调用时,这种情况会更加明显。
以一个复杂的业务流程为例,假设我们有一个电商系统的订单处理流程,它涉及多个步骤,并且每个步骤都有不同的实现逻辑。通过模板方法模式,我们将订单处理流程定义在一个抽象类OrderProcessor中:
public abstract class OrderProcessor {
// 模板方法,定义订单处理的整体流程
public final void processOrder() {
checkCustomerInfo();
validateOrder();
processPayment();
if (isGiftOrder()) {
prepareGift();
}
shipOrder();
updateOrderStatus();
}
// 具体方法,检查客户信息
protected void checkCustomerInfo() {
// 复杂的客户信息检查逻辑
System.out.println("检查客户信息");
}
// 抽象方法,验证订单,由子类实现
protected abstract void validateOrder();
// 抽象方法,处理支付,由子类实现
protected abstract void processPayment();
// 钩子方法,判断是否是礼品订单,默认不是
protected boolean isGiftOrder() {
return false;
}
// 抽象方法,准备礼品,由子类实现
protected abstract void prepareGift();
// 抽象方法,发货,由子类实现
protected abstract void shipOrder();
// 具体方法,更新订单状态
protected void updateOrderStatus() {
// 复杂的订单状态更新逻辑
System.out.println("更新订单状态");
}
}
然后,有多个具体的订单处理子类,如NormalOrderProcessor和GiftOrderProcessor,它们实现了抽象类中的抽象方法:
public class NormalOrderProcessor extends OrderProcessor {
@Override
protected void validateOrder() {
// 普通订单的验证逻辑
System.out.println("验证普通订单");
}
@Override
protected void processPayment() {
// 普通订单的支付处理逻辑
System.out.println("处理普通订单的支付");
}
@Override
protected void shipOrder() {
// 普通订单的发货逻辑
System.out.println("发送普通订单");
}
}
public class GiftOrderProcessor extends OrderProcessor {
@Override
protected void validateOrder() {
// 礼品订单的验证逻辑
System.out.println("验证礼品订单");
}
@Override
protected void processPayment() {
// 礼品订单的支付处理逻辑
System.out.println("处理礼品订单的支付");
}
@Override
protected boolean isGiftOrder() {
return true;
}
@Override
protected void prepareGift() {
// 准备礼品的逻辑
System.out.println("准备礼品");
}
@Override
protected void shipOrder() {
// 礼品订单的发货逻辑
System.out.println("发送礼品订单");
}
}
在这个例子中,要完全理解订单处理的整个流程,开发人员需要在OrderProcessor抽象类、NormalOrderProcessor子类和GiftOrderProcessor子类之间来回切换,查看不同类中的方法实现。特别是当每个方法中的逻辑比较复杂时,这种代码结构会增加阅读和理解代码的难度,导致代码的可读性下降。
在当今数字化教育蓬勃发展的时代,在线教育平台成为了教育领域的重要组成部分。为了满足广大学生和教师对在线学习和教学的需求,我们决定开发一个功能丰富、用户体验良好的在线教育平台。
该平台需要具备课程发布、学习、管理等核心功能。对于课程发布功能,教师能够方便快捷地将精心准备的课程内容上传到平台,包括课程视频、文档资料、练习题等,并详细填写课程的相关信息,如课程名称、课程简介、学习目标、适用人群等 。在学习功能方面,学生可以根据自己的兴趣和需求,在平台上搜索并选择合适的课程进行学习。学习过程中,学生能够流畅地观看课程视频,随时暂停、回放,还可以在线查看文档资料、完成练习题,并且能够与教师和其他学生进行互动交流,如提问、讨论等。而课程管理功能则要求平台能够对课程进行有效的分类、排序和检索,方便教师和管理员对课程进行管理,同时也便于学生快速找到自己需要的课程。此外,还需要对课程的学习进度、学生的学习情况等进行记录和统计,以便教师和管理员进行分析和评估。
在设计在线教育平台时,我们引入模板方法模式来优化课程相关功能的实现。首先,定义一个抽象的AbstractCourse类,它作为课程的抽象基类,定义了课程操作的模板方法courseProcess。这个模板方法包含了课程从发布到学习过程中的一系列基本步骤,如课程准备、课程展示、课程学习引导、课程互动等。其中,部分步骤是抽象方法,需要具体的课程子类去实现,因为不同类型的课程(如视频课程、文档课程、直播课程等)在这些步骤上的实现方式会有所不同。
具体课程子类继承自AbstractCourse类,并实现其中的抽象方法。例如,VideoCourse类用于表示视频课程,它实现了课程准备步骤中的视频上传和转码操作,以及课程展示步骤中的视频播放功能实现;DocumentCourse类用于表示文档课程,它实现了课程准备步骤中的文档上传和格式检查操作,以及课程展示步骤中的文档在线查看功能实现。
通过这种设计思路,我们搭建起了基于模板方法模式的课程功能架构。这种架构使得课程相关功能的实现更加清晰、模块化,提高了代码的复用性和可维护性。同时,也方便后续根据业务需求的变化,添加新的课程类型,只需要创建新的具体课程子类并实现相应的抽象方法即可,符合开闭原则。
下面是抽象课程类AbstractCourse的核心代码实现:
public abstract class AbstractCourse {
// 模板方法,定义课程操作的整体流程
public final void courseProcess() {
prepareCourse();
displayCourse();
guideLearning();
interactDuringLearning();
}
// 抽象方法,课程准备,由子类实现
protected abstract void prepareCourse();
// 抽象方法,课程展示,由子类实现
protected abstract void displayCourse();
// 具体方法,课程学习引导
protected void guideLearning() {
System.out.println("为学生提供课程学习指南,引导学生开始学习");
}
// 具体方法,课程学习过程中的互动
protected void interactDuringLearning() {
System.out.println("提供课程讨论区,方便学生与教师、其他学生互动交流");
}
}
在这段代码中,courseProcess方法是模板方法,它定义了课程操作的固定流程。prepareCourse和displayCourse是抽象方法,需要具体的课程子类去实现。guideLearning和interactDuringLearning是具体方法,提供了通用的课程学习引导和互动功能。
接下来是具体课程子类VideoCourse的代码实现:
public class VideoCourse extends AbstractCourse {
@Override
protected void prepareCourse() {
System.out.println("上传视频课程文件,并进行转码处理,以适应不同设备播放");
}
@Override
protected void displayCourse() {
System.out.println("在页面上展示视频课程播放器,学生可以点击播放学习");
}
}
在VideoCourse类中,实现了AbstractCourse类中的抽象方法prepareCourse和displayCourse。prepareCourse方法实现了视频课程文件的上传和转码操作,displayCourse方法实现了在页面上展示视频课程播放器的功能,以满足视频课程的特殊需求。
再看DocumentCourse类的代码实现:
public class DocumentCourse extends AbstractCourse {
@Override
protected void prepareCourse() {
System.out.println("上传文档课程文件,并检查文档格式是否正确");
}
@Override
protected void displayCourse() {
System.out.println("在页面上展示文档预览功能,学生可以在线查看文档内容");
}
}
DocumentCourse类同样实现了AbstractCourse类中的抽象方法。prepareCourse方法实现了文档课程文件的上传和格式检查,displayCourse方法实现了文档在线预览功能,体现了文档课程的特点。
通过以上核心代码的实现,我们可以清晰地看到模板方法模式在在线教育平台课程功能开发中的应用。抽象类定义了通用的课程操作流程,具体子类根据课程类型的不同实现了特定的步骤,使得代码结构更加清晰,易于维护和扩展。
通过在在线教育平台课程功能开发中应用模板方法模式,我们取得了显著的效果。一方面,代码的复用性得到了极大提高。抽象类中定义的模板方法和通用步骤被多个具体课程子类共享,避免了重复代码的编写,减少了开发工作量和维护成本。例如,guideLearning和interactDuringLearning方法在不同的课程子类中都能直接使用,无需重新实现。另一方面,系统的可维护性和扩展性也得到了增强。当需要修改课程操作的某个通用步骤时,只需在抽象类中进行修改,所有子类都会自动应用这些修改;当有新的课程类型加入时,只需要创建新的子类并实现相应的抽象方法,而不会影响到现有的课程功能。
从这个案例中我们得到的启示是,在软件开发过程中,要善于根据业务需求和系统特点选择合适的设计模式。模板方法模式适用于那些具有固定流程框架,但部分步骤实现会因具体情况而异的业务场景。通过合理应用该模式,可以有效地提高开发效率,提升代码质量,使系统更加健壮和灵活,更好地满足不断变化的业务需求。
模板方法模式作为一种行为型设计模式,定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现,使子类能够在不改变算法结构的前提下,重新定义该算法的某些特定步骤。这一模式主要包含抽象类和具体子类两个核心角色。抽象类定义了算法的骨架,即模板方法,其中包含了一系列按照特定顺序调用的基本方法,这些基本方法可以是抽象方法、具体方法或者钩子方法。抽象方法需由子类实现,代表了算法中变化的部分;具体方法已在抽象类中实现,子类可直接继承使用;钩子方法则是一种特殊方法,在抽象类中有默认实现,子类可根据需求选择性重写,用于控制算法的特定行为。具体子类实现抽象类中定义的抽象方法,完成算法中特定子类的步骤。
在 Java 代码实现中,通过抽象类和子类的继承关系,将通用的算法结构定义在抽象类中,变化部分留给子类实现。以制作咖啡和茶的示例来说,抽象类CaffeineBeverage定义了制作饮料的模板方法prepareRecipe,其中煮水和倒入杯中的方法是固定的具体方法,而冲泡和添加调料的方法是抽象方法,由Coffee和Tea子类分别实现,从而体现出不同饮料制作的差异。同时,模板方法通常被声明为final,防止子类重写破坏算法结构,抽象方法一般声明为protected,保证代码的安全性和封装性。此外,还可引入钩子方法,如在制作饮料时根据用户选择决定是否添加调料,增强代码的灵活性。
模板方法模式在软件开发和生活中都有广泛应用。在软件开发中,框架开发如 Spring 框架中的 JdbcTemplate,通过模板方法模式封装了数据库操作的通用流程,使开发者专注于业务 SQL 语句;业务逻辑处理中,电商系统的订单处理流程利用该模式,将订单创建、支付处理等通用步骤定义在抽象类中,不同类型订单的特殊逻辑由具体子类实现。在生活中,烹饪场景里炒菜的固定流程和不同菜品食材调料的添加,以及工作流程中员工入职流程的固定步骤和不同部门的特定入职安排,都体现了模板方法模式的应用。
模板方法模式具有提高代码复用性、增强代码可维护性和便于功能扩展等优点。通过将通用部分抽象到抽象类,避免了子类重复编写相同代码;当算法通用步骤或结构改变时,只需在抽象类中修改,不影响子类;新业务需求或功能扩展时,创建新子类实现抽象方法即可。然而,它也存在一些缺点,如子类灵活性受限,无法改变算法整体结构;代码可读性下降,算法实现分散在抽象类和子类中,增加阅读和理解难度。
在实际项目中,是否使用模板方法模式需要综合多方面因素进行考量。首先,要深入分析项目需求和业务场景,判断是否存在具有固定流程框架,但部分步骤实现会因具体情况而异的业务逻辑。若存在这样的情况,模板方法模式可能是一个合适的选择。例如,在一个内容管理系统中,文章发布、审核、推送等操作有固定流程,但不同类型文章(如新闻、博客、技术文章)在内容检查、格式处理等步骤上可能有不同实现,此时就可运用模板方法模式。
在设计抽象类时,务必精心规划模板方法和抽象方法。模板方法应准确无误地定义算法的核心骨架和执行顺序,确保其稳定性和通用性。抽象方法的设计要合理,能够清晰地分离出算法中变化的部分,为子类提供明确的扩展点。同时,要充分考虑抽象类中具体方法和钩子方法的使用。具体方法应封装那些通用的、不需要子类改变的功能;钩子方法则要设计得灵活,能够满足不同子类对算法特定行为的控制需求。以一个在线考试系统为例,抽象类中模板方法定义考试流程,抽象方法留给不同类型考试(如选择题考试、主观题考试)实现题目生成、判卷等功能,钩子方法可用于控制是否开启考试倒计时等。
对于子类的实现,要严格遵循抽象类的设计规范,确保实现的正确性和一致性。子类在实现抽象方法时,要充分理解抽象方法的目的和作用,根据具体业务需求提供准确的实现。同时,要注意避免在子类中重复实现抽象类中已有的功能,确保代码的复用性。例如,在一个图形绘制系统中,抽象类定义了图形绘制的基本流程,子类实现不同图形(如圆形、矩形、三角形)的绘制方法时,要按照抽象类的要求,准确实现绘制逻辑,而不是重新定义整个绘制流程。
随着技术的飞速发展,模板方法模式在新兴技术领域有望得到更广泛的应用和拓展。在人工智能和机器学习领域,模型训练和预测的流程有一定的固定框架,但不同模型(如神经网络、决策树、支持向量机)在数据预处理、模型参数调整、评估指标计算等方面有不同的实现方式,模板方法模式可以用于优化这些流程,提高代码的复用性和可维护性。例如,构建一个通用的机器学习模型训练框架,将数据加载、模型初始化、训练过程、评估过程等步骤定义在抽象类中,不同模型的具体实现由子类完成。
在大数据处理领域,数据采集、清洗、分析、存储等流程也存在相似的情况,模板方法模式可以帮助简化和规范这些流程。比如,在一个大数据分析平台中,抽象类定义数据处理的整体流程,子类根据不同数据源(如数据库、文件系统、网络日志)实现数据采集和清洗的具体逻辑。
未来,模板方法模式的研究方向可以侧重于如何更好地与其他设计模式结合,以应对更复杂的业务场景和需求。例如,与策略模式结合,在模板方法模式的基础上,进一步实现算法步骤的动态切换;与工厂方法模式结合,优化对象的创建过程,提高系统的灵活性和可扩展性。在一个电商系统中,将模板方法模式用于订单处理流程,同时结合策略模式,根据不同的支付方式(如支付宝、微信支付、银行卡支付)动态切换支付处理策略;结合工厂方法模式,根据不同的订单类型(如普通订单、团购订单、预售订单)创建相应的订单处理对象。
此外,还可以探索如何在分布式系统、云计算等环境下更好地应用模板方法模式,以提高系统的性能和可靠性。随着软件系统的规模和复杂度不断增加,模板方法模式将在软件架构设计中发挥更加重要的作用,为构建高效、灵活、可维护的软件系统提供有力支持。