01.如果接到一个任务,要求设计不同型号的悍马车
02.设计一个悍马车的抽象类(模具,车模)
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
public abstract void start();
//能发动,那还要能停下来,那才是真本事
public abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
public abstract void alarm();
//引擎会轰隆隆的响,不响那是假的
public abstract void engineBoom();
//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
public abstract void run();
}
03.具体不同版本的悍马,这个是抽象类的具体子类
H1 型号悍马的定义如下:
public class HummerH1Model extends HummerModel {
@Override
public void alarm() {
System.out.println("悍马H1鸣笛...");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎声音是这样在...");
}
@Override
public void start() {
System.out.println("悍马H1发动...");
}
@Override
public void stop() {
System.out.println("悍马H1停车...");
}
/*
* 这个方法是很有意思的,它要跑,那肯定要启动,停止了等,也就是要调其他方法
*/
@Override
public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
然后看悍马 H2 型号的实现:
public class HummerH2Model extends HummerModel {
@Override
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
@Override
public void engineBoom() {
System.out.println("悍马H2引擎声音是这样在...");
}
@Override
public void start() {
System.out.println("悍马H2发动...");
}
@Override
public void stop() {
System.out.println("悍马H1停车...");
}
/*
* H2要跑,那肯定要启动,停止了等,也就是要调其他方法
*/
@Override
public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
然后程序写到这里,你就看到问题了,run 方法的实现应该在抽象类上(子类改写的都一样),不应该在实现类上,好,我们修改一下类图和实现:
就把 run 方法放到了抽象类中,那代码也相应的改变一下,先看 HummerModel.java:
04.修改后的抽象类
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
public abstract void start();
//能发动,那还要能停下来,那才是真本事
public abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
public abstract void alarm();
//引擎会轰隆隆的响,不响那是假的
public abstract void engineBoom();
//那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑
public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
05.修改后的抽象类的子类:
public class HummerH1Model extends HummerModel {
@Override
public void alarm() {
System.out.println("悍马H1鸣笛...");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎声音是这样在...");
}
@Override
public void start() {
System.out.println("悍马H1发动...");
}
@Override
public void stop() {
System.out.println("悍马H1停车...");
}
}
public class HummerH2Model extends HummerModel {
@Override
public void alarm() {
System.out.println("悍马H2鸣笛...");
}
@Override
public void engineBoom() {
System.out.println("悍马H2引擎声音是这样在...");
}
@Override
public void start() {
System.out.println("悍马H2发动...");
}
@Override
public void stop() {
System.out.println("悍马H2停车...");
}
}
06.运行程序
public class Client {
public static void main(String[] args) {
//客户开着H1型号,出去遛弯了
HummerModel h1 = new HummerH1Model();
h1.run(); //汽车跑起来了;
//客户开H2型号,出去玩耍了
HummerModel h2 = new HummerH2Model();
h2.run();
}
}
07.客户只要在 run 的过程中,听到或看都成了呀,暴露那么多的方法干啥?把抽象方法保护起来
把抽象类上的四个方法设置为 protected 访问权限,好了,既然客户不关心这几个方法,而且这四个方法都是由子类来实现的,那就设置成 protected 模式。咦~,那还有个缺陷,run 方法既然子类都不修改,那是不是可以设置成 final 类型呢?是滴是滴,类图如下:
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
protected abstract void start();
//能发动,那还要能停下来,那才是真本事
protected abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
protected abstract void alarm();
//引擎会轰隆隆的响,不响那是假的
protected abstract void engineBoom();
//那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑
final public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
大家请看这个 run 方法,他定义了调用其他方法的顺序,并且子类是不能修改的,这个叫做模板方法;start、stop、alarm、engineBoom 这四个方法是子类必须实现的,而且这四个方法的修改对应了不同的类,这个叫做基本方法,基本方法又分为三种:在抽象类中实现了的基本方法叫做具体方法;在抽象类中没有实现,在子类中实现了叫做抽象方法,我们这四个基本方法都是抽象方法,由子类来实现的;还有一种叫做钩子方法,
。
“客户提出新要求了,那个喇叭想让它响就响,你看你设计的模型,车子一启动,喇叭就狂响,赶快修改一下”,确实是设计缺陷,呵呵,不过是我故意的,那我们怎么修改呢?看修改后的类图:
public abstract class HummerModel {
final public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//喇嘛想让它响就响,不想让它响就不响
if(this.isAlarm()){
this.alarm();
}
//到达目的地就停车
this.stop();
}
//钩子方法,默认喇叭是会响的 这个不是抽象方法
protected boolean isAlarm(){
return true;
}
}
钩子方法模式是由抽象类来实现的,子类可以重写的,H2 型号的悍马是不会叫的,喇叭是个摆设,看
HummerH2Model.java 代码:
public class HummerH2Model extends HummerModel {
//默认没有喇叭的
@Override
protected boolean isAlarm() {
return false;
}
}
那 H2 型号的模型都没有喇叭,就是按了喇叭也没有声音,那客户端这边的调用没有任何修改
public class Client {
public static void main(String[] args) {
HummerH2Model h2 = new HummerH2Model();
h2.run(); //H2型号的悍马跑起来
}
}
那 H1 又有所不同了,它的喇叭要不要响是由客户来决定,其实在类图上已经标明了 setAlarm 这个方
法,我们看 HummerH1Model.java 的代码:
public class HummerH1Model extends HummerModel {
private boolean alarmFlag = true; //是否要响喇叭
@Override
protected boolean isAlarm() {
return this.alarmFlag;
}
//要不要响喇叭,是有客户的来决定的 这个是子类的方法
public void setAlarm(boolean isAlarm){
this.alarmFlag = isAlarm;
}
}
这段代码呢修改了两个地方,一是重写了父类的 isAlarm()方法,一是增加了一个 setAlarm 方法,由
调用者去决定是否要这个功能,也就是喇叭要不要滴滴答答的响,哈哈,那我们看看 Client.java 的修改:
public class Client {
public static void main(String[] args) {
//客户开着H1型号,出去遛弯了
HummerH1Model h1 = new HummerH1Model();
h1.setAlarm(true);
h1.run(); //汽车跑起来了;
}
}
那我们总结一下模板方法模式,模板方法模式就是在模板方法中按照一个的规则和顺序调用基本方法,
具体到我们上面那个例子就是 run 方法按照规定的顺序(先调用 start,然后再调用 engineBoom,再调用
alarm,最后调用 stop)调用本类的其他方法,并且由 isAlarm 方法的返回值确定 run 中的执行顺序变更,
通用类图如下