设计模式的精髓:解耦。而模板设计模式是通过第三方进行解耦
什么是内聚、解耦大家可以看一下博主 小异常 的博文:https://blog.csdn.net/sun8112133/article/details/81040275
模板设计模式:(基于抽象类)在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类再不改变算法的前提下,重新定义算法中的某些步骤。
通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。
一个经典的模板设计模式的例子
星巴克咖啡冲泡法
1. 将水煮沸
2. 用沸水冲泡咖啡
3. 将咖啡倒进杯子
4. 加糖和牛奶
星巴克茶冲泡法
1. 将水煮沸
2. 用沸水浸泡茶叶
3. 把茶倒进杯子
4. 加柠檬
用代码简单实现就是
class Coffee{
void prepareRecipe(){
boilWater();
brewCoffeeGrings();
pourInCap();
addSugarAndMilk();
}
public void boilWater(){
System.out.println("将水煮沸");
}
public void brewCoffeeGrings(){
System.out.println("冲泡咖啡");
}
public void pourInCap(){
System.out.println("将咖啡倒进杯子");
}
public void addSugarAndMilk(){
System.out.println("加入糖和牛奶");
}
}
class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("将水煮沸");
}
public void steepTeaBag() {
System.out.println("冲泡茶");
}
public void pourInCup() {
System.out.println("将茶倒进杯子");
}
public void addLemon() {
System.out.println("加柠檬");
}
}
class Test{
public static void main(String[] args) {
Coffee coffee = new Coffee();
coffee.prepareRecipe();
Tea tea = new Tea();
tea.prepareRecipe();
}
}
让我们先从冲泡法入手。观察咖啡和茶的冲泡法我们会发现,两种冲泡法都采用了相同的算法:
1. 将水煮沸
2. 用热水泡饮料
3. 把饮料倒进杯子
4. 在饮料内加入适当的调料
实际上,浸泡(steep)和冲泡(brew)差异并不大。因此我们给它一个新的方法名称brew(),这样我们无论冲泡的是何种饮
料都可以使用这个方法。同样的,加糖、牛奶还是柠檬也很相似,都是在饮料中加入其它调料,因此我们也给它一
个通用名称addCondiments()。我们可以将这些重复的操作写在一个抽象类中,不同的地方写抽象方法,让实现的操作延迟到子类去具体完成。
/**
* 咖啡因饮料是一个抽象类
*/
abstract class CaffeineBeverage{
/**
* 声明为final的原因是我们不希望子类覆盖这个方法!
*/
final void prepareRecipe(){
boilWater();
brew();
pourInCap();
addCondiments();
}
/**
* 咖啡和茶处理这些方法不同,因此这两个方法必须被声明为抽象,留给子类实现
*/
abstract void brew();
abstract void addCondiments();
public void boilWater(){
System.out.println("将水煮沸");
}
public void pourInCap(){
System.out.println("将饮料倒进杯子");
}
}
class Coffee extends CaffeineBeverage{
public void brew(){
System.out.println("冲泡咖啡");
}
public void addCondiments(){
System.out.println("加入糖和牛奶");
}
}
class Tea extends CaffeineBeverage{
public void brew() {
System.out.println("冲泡茶");
}
public void addCondiments() {
System.out.println("加柠檬");
}
}
class Test{
public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
}
}
此时的类图:
我们刚刚实现的就是模板设计模式,它包含了实际的"模板方法"
模板方法定义了一个算法的步骤,并允许子类为一个或者多个步骤提供具体实现。
不好的茶或咖啡实现 模板方法提供的咖啡因饮料
Coffee或Tea主导一切,控制算法 由超类主导一切,它拥有算法,并且保护这个算法
Coffee与Tea之间存在重复代码 有超类的存在,因此可以将代码复用最大化
对于算法所做的代码改变,需要打开各个子类修改很多地方 算法只存在一个地方,容易修改
弹性差,新种类的饮料加入需要做很多工作 弹性高,新饮料的加入只需要实现自己的冲泡和加料方法即可
算法的知识和它的实现分散在许多类中 超类专注于算法本身,而由子类提供完整的实现。
一个完整的模板模式超类的定义:
/**
* 基类声明为抽象类的原因是
* 其子类必须实现其操作
*/
abstract class AbstractClass {
/**
* 模板方法,被声明为final以免子类改变这个算法的顺序
*/
final void templateMethod() {
}
/**
* 具体操作延迟到子类中实现
*/
abstract void primitiveOperation1();
abstract void primitiveOperation2();
/**
* 具体操作且共用的方法定义在超类中,可以被模板方法或子类直接使用
*/
final void concreteOperation() {
// 实现
}
/**
* 钩子方法是一类"默认不做事的方法"
* 子类可以视情况决定要不要覆盖它们。
*/
void hook() {
// 钩子方法
}
}
什么是钩子方法:钩子方法在我理解就是当我们需要用法从其他地方才能获取的值时,我们需要一个钩子把它钩过来,在下面的代码里就是我们从主类钩过来了用户的输入,再在其他类中根据这个输入做具体的操作,这样使我们的程序更加灵活。
import java.util.Scanner;
abstract class CaffeineBeverage{
final void prepareRecipe(){
boilWater();
brew();
pourInCap();
if(customerWantsCondiments()){
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
public void boilWater(){
System.out.println("将水煮沸");
}
public void pourInCap(){
System.out.println("将饮料倒进杯子");
}
/**
* 钩子方法
* 超类中通常是默认实现
* 子类可以选择性的覆写此方法
* @return
*/
public boolean customerWantsCondiments(){
return true;
}
}
class Coffee extends CaffeineBeverage{
public void brew(){
System.out.println("冲泡咖啡");
}
public void addCondiments(){
System.out.println("加入糖和牛奶");
}
/**
* 子类覆写了钩子函数,实现自定义功能
* @return
*/
public boolean customerWantsCondiments(){
String str = getUserInput();
if(str.equals("y"))
return true;
else
return false;
}
private String getUserInput() {
String answer = null;
System.out.println("您想要在咖啡中加入牛奶或糖吗 (y/n)?");
Scanner scanner = new Scanner(System.in);
answer = scanner.nextLine();
return answer;
}
}
class Test{
public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}