1.模板模式的定义
一个抽象类,公开定义了执行自己的方法/模板。它的子类可以按需重写方法实现,但调用需要按照抽象类中的定义的方式/模板进行。这种类型的设计模式属于行为性模式之一。用于定义一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
注意:
(行为者模式属于设计模式的大类,就像小米手机是属于手机类的这种映射关系,设计模式分为三大类,创建者模式、结构性模式、行为性模式,每个类各有细致的划分,这里不再表述,会在后续专栏中更新!模板模式就是行为性模式之一)。
话不多说,了解基础后,开始学习!come on bro!这里我先给你们打个预防针,它和下面还会讲到另一个行为性模式之一的策略模式很相似,但是实则大差特差,所以我才会选择将它们两个合为一篇文章去总结。
2.模板模式的组成
模板模式主要由抽象模板(Abstract Template)角色和具体模板(Concrete Template)角色组成。
抽象模板:定义了一个或多个抽象方法,以便让子类去实现,这些抽象方法叫做基本操作,它们是一个顶级逻辑的组成步骤,然后再定并且实现了一个模板方法,这个模板方法是一般是一个具体方法,是一个顶级逻辑的骨架,而逻辑的组成步骤就是抽象方法,然后抽象方法是由子类去实现。顶级逻辑也有可能调用一些方法。
具体模板:就是继承抽象模板,实现抽象模板类中的抽象方法,具体模板可以有多个,应对不同的实现,从而使顶级逻辑的实现各不相同。
3.模板模式代码
示图如下:
示例:
考虑一个制作咖啡和茶的例子。这两个过程的主要步骤是相同的,但某些细节不同。可以使用模板方法模式来设计这两个饮品的制作过程。
// 抽象类,定义模板方法和抽象方法
abstract class CaffeineBeverage {
// 模板方法,这里使用了final关键字,是因为不允许子类去修改逻辑骨架
final void prepareRecipe() {
brew();
addCondiments();
}
// 抽象方法,由子类实现
abstract void brew();
abstract void addCondiments();
}
// 具体子类,实现抽象方法
class Tea extends CaffeineBeverage {
void brew() {
System.out.println("Steeping the tea");
}
void addCondiments() {
System.out.println("Adding lemon");
}
}
class Coffee extends CaffeineBeverage {
void brew() {
System.out.println("Dripping coffee through filter");
}
void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
// 客户端代码
public class TemplateMethodExample {
public static void main(String[] args) {
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
System.out.println("-----");
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
模板方法模式的结构
模板方法模式通常包括以下几个部分:
-
抽象类 (Abstract Class) / 抽象模板:
- 定义了算法的骨架(模板方法)。
- 包含一个或多个抽象方法,这些方法将在子类中实现。
- 可以包含一些具体的方法,这些方法是算法的一部分,但不需要在子类中修改。
-
具体子类 (Concrete Class) / 具体模板:
- 实现了抽象类中的抽象方法,以提供算法的具体步骤。
- 子类可以根据需要重写抽象类中的某些步骤,但算法的整体结构仍由抽象类控制。
看完总结现在是不是恍然大悟啦!接下来我要继续讲解策略模式了。
4.策略模式的定义
策略模式也是行为性模式之一,其用意是针对一组算法,将每一个算法封装到具有共同性质的接口中,从而使这组算法相互替换。其主要目的就是替换if/else语句写法,并且可以随时替换。替换if/else语句的方法还有一种是利用枚举类,这里就当做扩展,想要了解,可以关注我,这几天我会尽快总结!谢谢关注!
5.策略模式的组成
策略模式主要有三个角色组成:环境角色(Context)、抽象策略角色(Strategy)和具体策略角色(ConcreteStrategy)。
环境角色:持有一个策略的引用,提供给客户端使用的类。
抽象策略角色:是一个抽象角色,通常由抽象或者接口实现,此角色给出所有具体策略类所需要的接口,这里其实使用接口的形式,因为java中类只能单一继承,不能多继承,所以使用接口,因为接口可以多继承,这样子类就具有优秀的拓展性。
具体策略角色:实现抽象策略类的方法。
示例图如下:
6.策略模式的代码
以计算器为例,如果我们想得到两个数字相加的和,我们需要用到"+"符号,得到相减的差,需要用到"-"符号等等。
虽然我们可以通过字符串比较使用if/else写成通用方法,但是计算的符号每次增加,我们就不得不加在原先的方法中进行增加相应的代码,如果后续计算方法增加、修改或删除,那么会使后续的维护变得困难。
但是在这些方法中,我们发现其基本方法是固定的,这时我们就可以通过策略模式来进行开发,可以有效避免通过if/else来进行判断,即使后续增加其他的计算规则也可灵活进行调整。
首先定义一个抽象策略角色,并拥有一个计算的方法。
interface CalculateStrategy {
int doOperation(int num1, int num2);
}
然后再定义加减乘除这些具体策略角色并实现方法,代码如下:
class OperationAdd implements CalculateStrategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
class OperationSub implements CalculateStrategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
class OperationMul implements CalculateStrategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
class OperationDiv implements CalculateStrategy {
@Override
public int doOperation(int num1, int num2) {
return num1 / num2;
}
}
再定一个环境角色,为客户端提供方法:
class calulatorContext {
private CalulateStrategy strategy;
public CalulatorContext(CalulateStrategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
测试代码如下:
策略模式优点:
- 扩展性好,同一种类算法,直接添加新的类。
- 灵活性好,可以对算法进行切换。
缺点:
- 如果策略过多,会增加系统复杂性。
- 客户端需要知道所有具体策略类才能进行调用。
到现在全都讲完啦,现在要开始对比两种行为型模式的区别
模板方法模式(Template Method Pattern)和策略模式(Strategy Pattern)都是行为型设计模式,它们确实在某些方面有相似之处,但本质上解决的问题和使用场景有所不同。下面将详细比较这两种模式的相似点和区别,并通过示例进一步说明。
相似点
-
封装变化:
- 两者都通过封装算法的不同部分来实现灵活性,允许在不改变整体结构的情况下改变部分行为。
-
提高代码复用性:
- 都旨在通过抽象化和模块化来提高代码的复用性,减少重复代码。
-
依赖抽象而非具体实现:
- 两种模式都依赖于抽象(如接口或抽象类),而不是具体的实现类,从而实现松耦合。
区别
-
控制反转的方向:
- 模板方法模式:控制权在抽象类中。抽象类定义了算法的骨架,具体步骤由子类实现。子类在父类的控制下实现特定步骤。
- 策略模式:控制权在客户端。客户端选择并切换不同的策略(算法),策略独立于使用它的上下文。
-
继承 vs 组合:
- 模板方法模式:依赖继承关系。子类通过继承抽象类来定制算法的某些步骤。
- 策略模式:依赖组合关系。上下文类持有策略对象,通过组合而不是继承来改变行为。
-
算法结构:
- 模板方法模式:适用于算法步骤固定,但某些步骤可以变化的场景。整体算法结构由抽象类控制。
- 策略模式:适用于多个算法可以互换的场景。不同策略可以在运行时动态切换。
-
灵活性:
- 策略模式:更加灵活,因为策略可以在运行时自由切换,不依赖继承层次结构。
- 模板方法模式:灵活性较低,因为需要通过继承来扩展,且固定的算法结构可能限制了变化。