模板方法模式
,又叫模板模式
,在一个抽象类中定义了一个执行它的其他方法的公开模板方法,子类可以按需重写抽象类的抽象方法【AbstractClass】
template方法
规定了如何调用operation2
、operation3
、operation4
这几个子方法,子方法可以是抽象方法(需要子类来实现),也可以是已经实现的方法【ConcreteClass、ConcreteClassB】
当要完成某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤可能有不同的实现,通常考虑用模板方法模式来处理
AbstractClass(抽象类)
:该角色负责实现模板方法,还声明在模板方法中所使用到的抽象方法ConcreteClass(具体类)
:负责具体实现AbstractClass角色中定义的抽象方法。这里实现的方法将会在AbstractClass角色的模板方法中被调用Client(客户端)
:使用具体类继承的模板方法编写制作豆浆的程序,说明如下:
【豆浆抽象类】
package com.test.template;
/**
* 抽象类,表示豆浆
*/
public abstract class SoyaMilk {
/**
* 模板方法, 模板方法可以做成final , 不让子类去覆盖
*/
final void make() {
select();
addCondiments();
soak();
beat();
}
/**
* 选材料
*/
void select() {
System.out.println("第一步:选择好的新鲜黄豆 ");
}
/**
* 添加不同的配料, 抽象方法, 子类具体实现
*/
abstract void addCondiments();
/**
* 浸泡
*/
void soak() {
System.out.println("第三步:黄豆和配料开始浸泡, 需要3小时 ");
}
/**
* 打碎
*/
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
}
}
【红豆豆浆】
package com.test.template;
/**
* 红豆豆浆
*/
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的红豆 ");
}
}
【花生豆浆】
package com.test.template;
/**
* 花生豆浆
*/
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的花生 ");
}
}
【主类】
package com.test.template;
public class Client {
public static void main(String[] args) {
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}
【运行】
----制作红豆豆浆----
第一步:选择好的新鲜黄豆
加入上好的红豆
第三步:黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆
加入上好的花生
第三步:黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
Process finished with exit code 0
在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
应用场景:希望可以制作纯豆浆,需要添加任何配料,使用钩子方法改造上面的程序
【豆浆抽象类】
package com.test.template.improve;
//抽象类,表示豆浆
public abstract class SoyaMilk {
/**
* 模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
*/
final void make() {
select();
if(customerWantCondiments()) {
// 如果需要添加配料
addCondiments();
}
soak();
beat();
}
/**
* 选材料
*/
void select() {
System.out.println("第一步:选择好的新鲜黄豆 ");
}
/**
* 添加不同的配料, 抽象方法, 子类具体实现
*/
abstract void addCondiments();
/**
* 浸泡
*/
void soak() {
System.out.println("第三步:黄豆和配料开始浸泡, 需要3小时 ");
}
/**
* 打碎
*/
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
}
/**
* 钩子方法,决定是否需要添加配料
* @return
*/
boolean customerWantCondiments() {
return true;
}
}
【纯豆浆】
package com.test.template.improve;
/**
* 纯豆浆
*/
public class PureSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
//空实现
}
/**
* 如果需要自定义钩子函数,就重写这个方法,不需要就不用重写
* @return
*/
@Override
boolean customerWantCondiments() {
return false;
}
}
【主类】
package com.test.template.improve;
public class Client {
public static void main(String[] args) {
System.out.println("----制作纯豆浆----");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
【运行】
----制作纯豆浆----
第一步:选择好的新鲜黄豆
第三步:黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
Process finished with exit code 0
【抽象类】
package com.test.template.Sample;
/**
* 抽象类AbstractDisplay
*/
public abstract class AbstractDisplay {
/**
* 交给子类去实现的抽象方法(1) open
*/
public abstract void open();
/**
* 交给子类去实现的抽象方法(2) print
*/
public abstract void print();
/**
* 交给子类去实现的抽象方法(3) close
*/
public abstract void close();
/**
* 本抽象类中实现的display方法,模板方法
*/
public final void display() {
// 首先打开…
open();
// 循环调用5次print
for (int i = 0; i < 5; i++) {
print();
}
// …最后关闭。这就是display方法所实现的功能
close();
}
}
【子类:CharDisplay】
package com.test.template.Sample;
/**
* CharDisplay是AbstractDisplay的子类
*/
public class CharDisplay extends AbstractDisplay {
/**
* 需要显示的字符
*/
private char ch;
public CharDisplay(char ch) {
// 保存在字段中
this.ch = ch;
}
public void open() {
// 显示开始字符"<<"
System.out.print("<<");
}
public void print() {
// 显示保存在字段ch中的字符
System.out.print(ch);
}
public void close() {
// 显示结束字符">>"
System.out.println(">>");
}
}
【子类:StringDisplay】
package com.test.template.Sample;
/**
* StringDisplay也是AbstractDisplay的子类
*/
public class StringDisplay extends AbstractDisplay {
/**
* 需要显示的字符串
*/
private String string;
/**
* 以字节为单位计算出的字符串长度
*/
private int width;
public StringDisplay(String string) {
this.string = string;
// 将字符串的字节长度也保存在字段中,以供后面使用
this.width = string.getBytes().length;
}
public void open() {
// 调用该类的printLine方法画线
printLine();
}
public void print() {
// 给保存在字段中的字符串前后分别加上"|"并显示出来
System.out.println("|" + string + "|");
}
public void close() {
// 与open方法一样,调用printLine方法画线
printLine();
}
/**
* 被open和close方法调用。由于可见性是private,因此只能在本类中被调用
*/
private void printLine() {
// 显示表示方框的角的"+"
System.out.print("+");
for (int i = 0; i < width; i++) {
// 显示width个"-",和"+"一起组成边框
System.out.print("-");
}
// 显示表示方框的角的"+"
System.out.println("+");
}
}
【主类】
package com.test.template.Sample;
public class Main {
public static void main(String[] args) {
// 生成一个持有'H'的CharDisplay类的实例
AbstractDisplay d1 = new CharDisplay('H');
// 生成一个持有"Hello, world."的StringDisplay类的实例
AbstractDisplay d2 = new StringDisplay("Hello, world.");
// 生成一个持有"你好,世界。"的StringDisplay类的实例
AbstractDisplay d3 = new StringDisplay("你好,世界。");
/*
* 由于d1、d2和d3都是AbstractDisplay类的子类
* 可以调用继承的display方法
* 实际的程序行为取决于CharDisplay类和StringDisplay类的具体实现
*/
d1.display();
d2.display();
d3.display();
}
}
【运行】
<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
+------------------+
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
+------------------+
Process finished with exit code 0
实现类有一个模板方法
另一个钩子方法
除此之外,java.io.InputStream类中也使用了Template Method
【优点】
【不足】
问:如果想要让示例程序中的open、print、close方法可以被具有继承关系的类和同一程序包中的类调用,但是不能被无关的其他类调用,应当怎么做呢?
答:使用protected关键字修饰这些方法,不要使用public。
【Java四种权限修饰符】
public | protected | (default) | private | |
---|---|---|---|---|
类本身 | √ | √ | √ | √ |
同一个包下的类 | √ | √ | √ | × |
不同包,但是我的子类 | √ | √ | × | × |
不同包非子类 | √ | × | × | × |
注:default不需要写出来,不写权限修饰符默认就是defaunt
问:Java中的接口与抽象类很相似。接口同样也是抽象方法的集合,但是在模板方法模式中,我们却无法使用接口来扮演AbstractClass 角色,请问这是为什么呢?
答:因为使用接口无法实现模板方法和其他的方法。