对模板方法模式的理解
-
- 一、场景
-
- 1、题目【[来源](https://kamacoder.com/problempage.php?pid=1087)】
-
- [1.1 题目描述](#1.1 题目描述)
- [1.2 输入描述](#1.2 输入描述)
- [1.3 输出描述](#1.3 输出描述)
- [1.4 输入示例](#1.4 输入示例)
- [1.5 输出示例](#1.5 输出示例)
- 二、不采用模板方法模式
- 三、采用模板方法模式
- 四、总结
一、场景
1、题目【来源】
1.1 题目描述
小明喜欢品尝不同类型的咖啡,她发现每种咖啡的制作过程有一些相同的步骤,他决定设计一个简单的咖啡制作系统,使用模板方法模式定义咖啡的制作过程。系统支持两种咖啡类型:美式咖啡(American Coffee)和拿铁(Latte)。
咖啡制作过程包括以下步骤:
- 研磨咖啡豆 Grinding coffee beans
- 冲泡咖啡 Brewing coffee
- 添加调料 Adding condiments
其中,美式咖啡和拿铁的调料添加方式略有不同, 拿铁在添加调料时需要添加牛奶Adding milk
1.2 输入描述
多行输入,每行包含一个数字,表示咖啡的选择(1 表示美式咖啡,2 表示拿铁)。
1.3 输出描述
根据每行输入,输出制作咖啡的过程,包括咖啡类型和各个制作步骤,末尾有一个空行。
1.4 输入示例
1
2
1.5 输出示例
Making American Coffee:
Grinding coffee beans
Brewing coffee
Adding condiments
Making Latte:
Grinding coffee beans
Brewing coffee
Adding milk
Adding condiments
二、不采用模板方法模式
1、代码
java
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()) {
int n = scanner.nextInt();
if (n == 1) {
System.out.println("Making American Coffee:");
System.out.println("Grinding coffee beans");
System.out.println("Brewing coffee");
System.out.println("Adding condiments");
} else if (n == 2) {
System.out.println("Making Latte:");
System.out.println("Grinding coffee beans");
System.out.println("Brewing coffee");
System.out.println("Adding milk");
System.out.println("Adding condiments");
} else {
throw new RuntimeException("must be 1 or 2");
}
System.out.println();
}
}
}
2、问题
- 虽然是制作不同的咖啡,但制作过程中,有些步骤是一样的。因此,可以复用这些步骤,减少代码冗余。
- 咖啡是抽象的,但美式咖啡、拿铁是具体的。很容易想到"抽象类-实现子类"这样的代码结构。在抽象类中规范步骤,子类去覆写细节。
三、采用模板方法模式
1、代码
- 模板
java
public interface Coffee {
CoffeeEnum gotCoffeeEnum();
void create();
}
public abstract class AbstractCoffee implements Coffee {
@Override
public final void create() {
System.out.printf("Making %s:%n", gotCoffeeEnum().getDesc());
System.out.println("Grinding coffee beans");
System.out.println("Brewing coffee");
addCondiments();
}
protected void addCondiments() {
System.out.println("Adding condiments");
}
}
- 实现类
java
@Getter
@AllArgsConstructor
public enum CoffeeEnum {
AMERICAN_COFFEE("American Coffee", 1),
LATTE("Latte", 2)
;
private final String desc;
private final int code;
public static CoffeeEnum getCoffeeEnum(int code) {
for (CoffeeEnum coffeeEnum : CoffeeEnum.values()) {
if (coffeeEnum.getCode() == code) {
return coffeeEnum;
}
}
return null;
}
}
public class AmericanCoffee extends AbstractCoffee {
@Override
public CoffeeEnum gotCoffeeEnum() {
return CoffeeEnum.AMERICAN_COFFEE;
}
}
public class LatteCoffee extends AbstractCoffee {
@Override
public CoffeeEnum gotCoffeeEnum() {
return CoffeeEnum.LATTE;
}
@Override
protected void addCondiments() {
System.out.println("Adding milk");
System.out.println("Adding condiments");
}
}
- 外观模式,对各种Coffee的实现类进行封装:
java
public class CoffeeFacade {
private List<Coffee> coffees;
public CoffeeFacade() {
this.coffees = new ArrayList<>();
this.coffees.add(new AmericanCoffee());
this.coffees.add(new LatteCoffee());
}
public void makeCoffee(int code) {
coffees.stream()
.filter(coffee -> coffee.gotCoffeeEnum().getCode() == code)
.findFirst()
.ifPresent(Coffee::create);
}
}
- 客户端:
java
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
CoffeeFacade coffeeFacade = new CoffeeFacade();
while (scanner.hasNextInt()) {
int n = scanner.nextInt();
coffeeFacade.makeCoffee(n);
System.out.println();
}
}
}
四、总结
-
在企业开发中,模板方法模式还是很常见的。通过分析需求,发现类与类的关系呈现下图所示,就可以在抽象类中定义通用步骤(模板),子类去覆写具体细节即可。
-
当然了,模板方法模式也存在一个明显的弊端:一旦某个实现类不适用当前模板了,不得不去修改抽象类的通用步骤时,便会影响现有实现类(复用的代价)。
-
比较好的做法是,定义通用步骤时,步骤要足够抽象,而不能太具体太细了,这样可以给子类足够的灵活性。