模拟场景:一个xxx模型公司要建立一批car。而这些car是有一个统一的模板的,都是由统一的特性的,只是各种特性的在不同的car中各不相同。
先分析一下,既然这些car是有一个统一的模板的,模板上定义了这些car统一的特性,如可以开动start、可以停止stop、可以鸣笛alarm、可以启动引擎engineBoom、可以跑动run。
那么转换为具体的类文件,也就是说首先要有一个抽象类CarModel,表示car创建的模板,在该类中定义一系列的特性,即定义一系列方法start()、stop()、alart()、engineBoon()、run(),但是要注意的是,run()方法时需要其他方法支持的,也就是说run之前要先开动car(start),启动引擎(engineBoom),鸣笛(alarm),最后要停下来(stop),那么很明显,所有的car在跑动(run)的时候都是要进行同样的一系列操作。所以run()是共有的,也就是说不能定义为abstract,而且既然这些操作要在模板中就定义好,那么就应该把该方法设置为final,表示子类不能进行覆写,这样就可以防止子类的误操作,还有其他特性要统一设置为abstract。
注:这里只是简单的举例,这里我的模板规定了在run之前要调用一系列的操作。在实际中要按实际情况进行设计。
CarModel:car建立的模板
1 package com.zqz.dp.template; 2 /** 3 * @author Qin 生产car的模型,所有的才都按照此模板进行设计 4 */ 5 public abstract class CarModel { 6 /** 7 * 首先car要可以启动起来 8 */ 9 protected abstract void start();10 /**11 * 除了可以开动,car也要可以停止下来12 */13 protected abstract void stop();14 /**15 * car还要可以鸣笛16 */17 public abstract void alarm();18 /**19 * car还要有引擎20 */21 protected abstract void engineBoom();22 /**23 * 定义完规定之后,car要开始进行建立模型,下面就是进行具体的操作。设置为final是为了防止子类破坏进行覆写24 */25 protected final void run() {26 this.start(); // car开始启动27 this.engineBoom(); // 引擎启动28 this.alarm(); // 开始鸣笛29 this.stop(); // car停下来30 }31 }
Car1:根据模板CarModel建立的第一辆car1
1 package com.zqz.dp.template; 2 /** 3 * @author Qin 4 * 第一辆按照CarModel建立的car1 5 */ 6 public class Car1 extends CarModel { 7 @Override 8 public void start() { 9 System.out.println("car1启动");10 }11 @Override12 public void stop() {13 System.out.println("car1停下来");14 }15 @Override16 public void alarm() {17 System.out.println("car1鸣笛");18 }19 @Override20 public void engineBoom() {21 System.out.println("car1启动引擎");22 }23 }
Car2:根据模板CarModel建立的第一辆car2
1 package com.zqz.dp.template; 2 /** 3 * @author Qin 第二辆按照CarModel建立的car2 4 */ 5 public class Car2 extends CarModel { 6 @Override 7 public void start() { 8 System.out.println("car2启动"); 9 }10 @Override11 public void stop() {12 System.out.println("car2停下来");13 }14 @Override15 public void alarm() {16 System.out.println("car2鸣笛");17 }18 @Override19 public void engineBoom() {20 System.out.println("car2启动引擎");21 }22 }
Client:场景类,开始建立car并让car跑动起来
1 package com.zqz.dp.template; 2 /** 3 * @author Qin 4 * 场景类,主要是按照模板CarModel生成不同的car,并开动起来 5 */ 6 public class Client { 7 public static void main(String[] args) { 8 System.out.println("-----------建立第一辆car-------------"); 9 CarModel car1=new Car1(); //根据模板建立第一辆car110 car1.run(); //开始建立car,让car跑动起来11 System.out.println("-----------建立第二辆car-------------");12 CarModel car2=new Car2(); //根据模板建立第一辆car213 car2.run(); //开始建立car,让car跑动起来14 }15 }
模板方法的定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的算法。
模板方法的优点:
1、 封装不变对象,拓展可变部分:即把不变的部分封装在父类中,这里指的是建立一辆car有什么特性。可变部分交给子类去继承,拓展实现。也就是说如果现在要建立一辆car3,只需要继承CarModel,实现具体方法即可。
2、 提取公共代码部分,便于维护:即把共有的,不应该改变的对象封装在父类(抽象类)中,并定义为final,表示不可改变,而其他可变的部分定义成abstract,让子类自己去实现。
3、 行为由父类控制,子类实现:基本的方法有子类进行实现
模板方法的缺点
在我们传统的设计中,抽象类一般只负责声明最抽象、一般的属性和方法,实现类完成具体的实现。但是在模板方法中,抽象类却定义了部分抽象方法,由子类去实现。也定义了普通的方法,调用抽象方法。也就说说子类覆写的结果会影响到父类的方法。在复杂项目中会带来阅读的难度。
模板方法的使用场景:
1、 多个子类有相同的行为操作,且逻辑基本相同
2、 重要,复杂的算法
3、 重构时可以考虑此模式
模板方法的拓展:
模拟场景:现在发现,每一辆car都会产生鸣笛,这样造成了很大的噪音。现在有如下要求,car1根据客户的需求来定义是否需要鸣笛的特性,而car2不存在鸣笛的特性。那么该怎么进行设计呢?
分析,其实大方向还是不需要改变的,只是现在需要根据子类来决定父类的操作了,那么我们就可以跟设计一个run方法一样在CarModel中设计一个方法isAlarm(),来返回boolean,用来判断父类是否执行alarm()方法。记住应该是非abstract,非final的,因为car2是不需要鸣笛的,那么我们在父类中就可以默认设置为true,如果有一辆car是需要鸣笛的,那么就可以不用进行覆写。而car1是根据情况进行操作的,所以要进行覆写。其实这个方法就叫做“钩子方法”(Hook Method)。有了钩子方法的模板方法模式才算完美,这样就可以由子类的一个返回值来确定父类的执行结果。
修改CarModel
1 package com.zqz.dp.template.hookmethod; 2 /** 3 * @author Qin 生产car的模型,所有的才都按照此模板进行设计 4 */ 5 public abstract class CarModel { 6 /** 7 * 首先car要可以启动起来 8 */ 9 protected abstract void start();10 /**11 * 除了可以开动,car也要可以停止下来12 */13 protected abstract void stop();14 /**15 * car还要可以鸣笛16 */17 public abstract void alarm();18 /**19 * car还要有引擎20 */21 protected abstract void engineBoom();22 /**23 * 判断是否需要鸣笛alarm这个特性24 */25 protected boolean isAlarm(){26 return false; //默认是不需要鸣笛的27 }28 /**29 * 定义完规定之后,car要开始进行建立模型,下面就是进行具体的操作。设置为final是为了防止子类破坏进行覆写30 */31 protected final void run() {32 this.start(); // car开始启动33 this.engineBoom(); // 引擎启动34 if(this.isAlarm()){ //如果需要鸣笛--具体是否需要交给子类35 this.alarm(); // 开始鸣笛36 }37 this.stop(); // car停下来38 }39 }
修改的car1
1 package com.zqz.dp.template.hookmethod; 2 /** 3 * @author Qin 4 * 第一辆按照CarModel建立的car1 5 */ 6 public class Car1 extends CarModel { 7 private boolean alarmflag=true; //标志,用于判断是否需要进行鸣笛的操作,默认需要 8 @Override 9 public void start() {10 System.out.println("car1启动");11 }12 @Override13 public void stop() {14 System.out.println("car1停下来");15 }16 @Override17 public void alarm() {18 System.out.println("car1鸣笛");19 }20 @Override21 public void engineBoom() {22 System.out.println("car1启动引擎");23 }24 @Override25 public boolean isAlarm() {26 return this.alarmflag;//交给alarmflag,外部设置27 }28 public boolean isAlarmflag() {29 return alarmflag;30 }31 public void setAlarmflag(boolean alarmflag) {32 this.alarmflag = alarmflag;33 }34 }
修改的car2
1 package com.zqz.dp.template.hookmethod; 2 /** 3 * @author Qin 第二辆按照CarModel建立的car2 4 */ 5 public class Car2 extends CarModel { 6 @Override 7 public void start() { 8 System.out.println("car2启动"); 9 }10 @Override11 public void stop() {12 System.out.println("car2停下来");13 }14 @Override15 public void alarm() {16 System.out.println("car2鸣笛");17 }18 @Override19 public void engineBoom() {20 System.out.println("car2启动引擎");21 }22 @Override23 protected boolean isAlarm() {24 return false; // 不需要鸣笛alarm这个特性25 }26 }
修改的场景类Client
1 package com.zqz.dp.template.hookmethod; 2 import java.io.BufferedReader; 3 import java.io.IOException; 4 import java.io.InputStreamReader; 5 /** 6 * @author Qin 场景类,主要是按照模板CarModel生成不同的car,并开动起来 7 */ 8 public class Client { 9 public static void main(String[] args) {10 System.out.println("-----------建立第一辆car-------------");11 Car1 car1 = new Car1(); // 建立第一辆car112 BufferedReader buf = new BufferedReader(13 new InputStreamReader(System.in)); // 输入流,键盘输入14 System.out.println("是否需要鸣笛alarm这一特性,输入0代表不需要,输入其他代表需要");15 String str=null;16 try {17 str = buf.readLine(); //等待输入18 } catch (IOException e) {19 e.printStackTrace();20 } 21 if(str.equals("0")){22 car1.setAlarmflag(false); //设置不用鸣笛alarm这一特性23 }24 car1.run(); // 开始建立car,让car跑动起来25 System.out.println("-----------建立第二辆car-------------");26 CarModel car2 = new Car2(); // 根据模板建立第一辆car227 car2.run(); // 开始建立car,让car跑动起来28 }29 }