1 辉煌工程——制造悍马
周三,9:00,我刚刚坐到位置上,打开电脑准备开始干活。
“小三,小三,叫一下其他同事,到会议室开会”,老大跑过来吼,带着坏笑。还没等大家坐稳,老大就开讲了:
“告诉大家一个好消息,昨天终于把××模型公司的口子打开了,要我们做悍马模型,虽然是第一个车辆模型,但是我们有能力、有信心做好,我们一定要……”(中间省略20分钟的讲话,如果你听过领导人的讲话,这个你应该能够续上)
动员工作做完了,那就开始压任务了。“这次时间是非常紧张的,只有一个星期的时间,小三,你负责在一个星期的时间把这批10万车模(注:车模是车辆模型的意思,不是香车美女那个车模)建设完成……”
“一个星期?这个……是真做不完,要做分析,做模板,做测试,还要考虑扩展性、稳定性、健壮性等,时间实在是太少了。”还没等老大说完,我就急了,再不急我的小命就折在上面了!
“那这样,只做最基本的实现,不考虑太多的问题,怎么样?”老大又把我弹回去了。
“只作基本实现?那……”
唉,领导已经布置任务了,那就开始拼命地做吧。然后就开始准备动手做,在做之前先介绍一下我们公司的背景,我们公司是做模型生产的,做过桥梁模型、建筑模型、机械模
型,甚至是一些政府、军事的机密模型,这个不能细说,绝密。公司的主要业务就是把实物按照一定的比例缩小或放大,用于试验、分析、量化或者是销售,等等,上面提到的××模型
公司是专门销售车辆模型的公司,自己没有生产企业,全部是代工。我们公司是第一次从××模型公司接单,那我怎么着也要把活干好,可时间有限,任务量又巨大,怎么办?
既然领导都说了,不考虑扩展性,那好办,先按照最一般的经验设计类图,如下图所示:
非常简单的实现,悍马车有两个型号,H1和H2。按照需求,只需要悍马模型,那好我就给你悍马模型,先写个抽象类,然后两个不同型号的模型实现类,通过简单的继承就可以
实现业务要求。我们先从抽象类开始编写,抽象悍马模型如代码清单如下所示。
1 public abstract class HummerModel { 2 3 /* 4 * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 5 * 是要能够发动起来,那这个实现要在实现类里了 6 */ 7 public abstract void start(); 8 9 //能发动,那还要能停下来,那才是真本事 10 public abstract void stop(); 11 12 //喇叭会出声音,是滴滴叫,还是哔哔叫 13 public abstract void alarm(); 14 15 //引擎会轰隆隆的响,不响那是假的 16 public abstract void engineBoom(); 17 18 //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑 19 public abstract void run(); 20 21 }
在抽象类中,我们定义了悍马模型都必须具有的特质:能够发动、停止,喇叭会响,引擎可以轰鸣,而且还可以停止。但是每个型号的悍马实现是不同的,H1型号的悍马如代码
清单如下所示。
1 public class HummerH1Model extends HummerModel { 2 //H1型号的悍马车鸣笛 3 public void alarm() { 4 System.out.println("悍马H1鸣笛..."); 5 } 6 7 //引擎轰鸣声 8 public void engineBoom() { 9 System.out.println("悍马H1引擎声音是这样在..."); 10 } 11 12 //汽车发动 13 public void start() { 14 System.out.println("悍马H1发动..."); 15 } 16 17 //停车 18 public void stop() { 19 System.out.println("悍马H1停车..."); 20 } 21 22 //开动起来 23 public void run(){ 24 25 //先发动汽车 26 this.start(); 27 28 //引擎开始轰鸣 29 this.engineBoom(); 30 31 //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭 32 this.alarm(); 33 34 //到达目的地就停车 35 this.stop(); 36 37 } 38 39 }
大家注意看run()方法,这是一个汇总的方法,一个模型生产成功了,总要拿给客户检测吧,怎么检测?“是骡子是马,拉出去溜溜”,这就是一种检验方法,让它跑起来!通过run()
这样的方法,把模型的所有功能都测试到了。
H2型号悍马如代码清单如下所示:
1 public class HummerH2Model extends HummerModel { 2 //H2型号的悍马车鸣笛 3 public void alarm() { 4 System.out.println("悍马H2鸣笛..."); 5 } 6 7 //引擎轰鸣声 8 public void engineBoom() { 9 System.out.println("悍马H2引擎声音是这样在..."); 10 } 11 12 //汽车发动 13 public void start() { 14 System.out.println("悍马H2发动..."); 15 } 16 17 //停车 18 public void stop() { 19 System.out.println("悍马H2停车..."); 20 } 21 22 //开动起来 23 public void run(){ 24 25 //先发动汽车 26 this.start(); 27 28 //引擎开始轰鸣 29 this.engineBoom(); 30 31 //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭 32 this.alarm(); 33 34 //到达目的地就停车 35 this.stop(); 36 } 37 38 }
好了,程序编写到这里,已经发现问题了,两个实现类的run()方法都是完全相同的,那这个run()方法的实现应该出现在抽象类,不应该在实现类上,抽象是所有子类的共性封装。
注意 在软件开发过程中,如果相同的一段代码复制过两次,就需要对设计产生怀疑,架构师要明确地说明为什么相同的逻辑要出现两次或更多次。
好,问题发现了,我们就需要马上更改,修改后的类图如图如下所示。
1 public abstract class HummerModel { 2 3 /* 4 * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 5 * 是要能够发动起来,那这个实现要在实现类里了 6 */ 7 public abstract void start(); 8 9 //能发动,那还要能停下来,那才是真本事 10 public abstract void stop(); 11 12 //喇叭会出声音,是滴滴叫,还是哔哔叫 13 public abstract void alarm(); 14 15 //引擎会轰隆隆的响,不响那是假的 16 public abstract void engineBoom(); 17 18 //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑 19 public void run(){ 20 //先发动汽车 21 this.start(); 22 23 //引擎开始轰鸣 24 this.engineBoom(); 25 26 //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭 27 this.alarm(); 28 29 //到达目的地就停车 30 this.stop(); 31 } 32 33 }
在抽象的悍马模型上已经定义了run()方法的执行规则,先启动,然后引擎立刻轰鸣,中间还要按一下喇叭,制造点噪声(要不就不是名车了)。然后停车,它的两个具体实现类就
不需要实现run()方法了,只要把代码清单10-2、代码清单10-3上的run()方法删除即可,不再赘述代码。
场景类实现的任务就是把生产出的模型展现给客户,其源代码如代码清单如下所示。
public class Client { public static void main(String[] args) { //牛叉公司要H1型号的悍马 HummerModel h1 = new HummerH1Model(); //H1模型演示 h1.run(); } }
运行结果如下:
悍马H1发动...
悍马H1引擎声音是这样的...
悍马H1鸣笛...
悍马H1停车...
目前客户只要看H1型号的悍马车,没问题,生产出来,同时可以运行起来给他看看。
非常简单,那如果我告诉你这就是模板方法模式你会不会很不屑呢?就这模式,太简单了,我一直在使用呀!是的,你经常在使用,但你不知道这是模板方法模式,那些所谓的高手就
可以很牛地说:“用模板方法模式就可以实现”,你还要很崇拜地看着,哇,牛人,模板方法模式是什么呀?这就是模板方法模式。
2 模板方法模式的定义
模板方法模式(Template Method Pattern)是如此简单,以致让你感觉你已经能够掌握其精髓了。其定义如下:
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template
Method lets subclasses redefine certain steps of an algorithm without changing the algorithm'sstructure.
(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)
模板方法模式的通用类图如图所示:
模板方法模式确实非常简单,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。其中,AbstractClass叫做抽象模板,它的方法分为两类:
● 基本方法
基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
● 模板方法
可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
在类图中还有一个角色:具体模板。ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
我们来看其通用代码,AbstractClass如代码清单如下所示。
抽象模板类
1 public abstract class AbstractClass { 2 3 //基本方法 4 protected abstract void doSomething(); 5 6 //基本方法 7 protected abstract void doAnything(); 8 9 //模版方法 10 public void templateMethod(){ 11 /* 12 * 调用基本方法,完成相关的逻辑 13 */ 14 this.doAnything(); 15 this.doSomething(); 16 } 17 }
具体模板如代码清单如下所示。
具体模板类
public class ConcreteClass1 extends AbstractClass { //实现基本方法 protected void doAnything() { //业务逻辑处理 } protected void doSomething() { //业务逻辑处理 } }
&
public class ConcreteClass2 extends AbstractClass { //实现基本方法 protected void doAnything() { //业务逻辑处理 } protected void doSomething() { //业务逻辑处理 } }
场景类
public class Client { public static void main(String[] args) { AbstractClass class1 = new ConcreteClass1(); AbstractClass class2 = new ConcreteClass2(); //调用模版方法 class1.templateMethod(); class2.templateMethod(); } }
注意 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问
权限。
3 模板方法模式的应用
3.1 模板方法模式的优点
● 封装不变部分,扩展可变部分
把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。在悍马模型例子中,是不是就非常容易扩展?例如增加一个H3型号的悍马模型,很容易呀,增加一个子类,实现父类的基本方法就可以了。
● 提取公共部分代码,便于维护
我们例子中刚刚走过的弯路就是最好的证明,如果我们不抽取到父类中,任由这种散乱的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码!
● 行为由父类控制,子类实现
基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
3.2 模板方法模式的缺点
按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类
实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
3.3模板方法模式的使用场景
● 多个子类有公有的方法,并且逻辑基本相同时。
● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。
4 模板方法模式的扩展
到目前为止,这两个模型都稳定地运行,突然有一天,老大急匆匆地找到了我:“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了!客户提出H1型号的悍马喇叭
想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”自己惹的祸,就要想办法解决它,稍稍思考一下,解决办法有了,先画出类图,如图
类图改动似乎很小,在抽象类HummerModel中增加了一个实现方法isAlarm,确定各个型号的悍马是否需要声音,由各个实现类覆写该方法,同时其他的基本方法由于不需要对外提
供访问,因此也设计为protected类型。其源代码如代码清单。
扩展后的抽象模板类
1 public abstract class HummerModel { 2 3 /* 4 * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正 5 * 是要能够发动起来,那这个实现要在实现类里了 6 */ 7 protected abstract void start(); 8 9 //能发动,那还要能停下来,那才是真本事 10 protected abstract void stop(); 11 12 //喇叭会出声音,是滴滴叫,还是哔哔叫 13 protected abstract void alarm(); 14 15 //引擎会轰隆隆的响,不响那是假的 16 protected abstract void engineBoom(); 17 18 //那模型应该会跑吧,别管是人退的,还是电力驱动,总之要会跑 19 final public void run() { 20 21 //先发动汽车 22 this.start(); 23 24 //引擎开始轰鸣 25 this.engineBoom(); 26 27 //要让它叫的就是就叫,喇嘛不想让它响就不响 28 if(this.isAlarm()){ 29 this.alarm(); 30 } 31 32 33 //到达目的地就停车 34 this.stop(); 35 } 36 37 //钩子方法,默认喇叭是会响的 38 protected boolean isAlarm(){ 39 return true; 40 } 41 }
在抽象类中,isAlarm是一个实现方法。其作用是模板方法根据其返回值决定是否要响喇叭,子类可以覆写该返回值,由于H1型号的喇叭是想让它响就响,不想让它响就不响,由人控制,其源代码如代码清单。
代码清单10-10 扩展后的H1悍马
1 public class HummerH1Model extends HummerModel { 2 private boolean alarmFlag = true; //是否要响喇叭 3 4 @Override 5 protected void alarm() { 6 System.out.println("悍马H1鸣笛..."); 7 } 8 9 @Override 10 protected void engineBoom() { 11 System.out.println("悍马H1引擎声音是这样在..."); 12 } 13 14 @Override 15 protected void start() { 16 System.out.println("悍马H1发动..."); 17 } 18 19 @Override 20 protected void stop() { 21 System.out.println("悍马H1停车..."); 22 } 23 24 protected boolean isAlarm() { 25 return this.alarmFlag; 26 } 27 28 //要不要响喇叭,是有客户的来决定的 29 public void setAlarm(boolean isAlarm){ 30 this.alarmFlag = isAlarm; 31 } 32 33 }
只要调用H1型号的悍马,默认是有喇叭响的,当然你可以不让喇叭响,通过
isAlarm(false)就可以实现。H2型号的悍马是没有喇叭声响的,其源代码如代码清单10-11所
示。
代码清单10-11 扩展后的H2悍马
1 public class HummerH2Model extends HummerModel { 2 3 protected void alarm() { 4 System.out.println("悍马H2鸣笛..."); 5 } 6 7 protected void engineBoom() { 8 System.out.println("悍马H2引擎声音是这样在..."); 9 } 10 11 protected void start() { 12 System.out.println("悍马H2发动..."); 13 } 14 15 protected void stop() { 16 System.out.println("悍马H2停车..."); 17 } 18 19 20 //默认没有喇叭的 21 protected boolean isAlarm() { 22 return false; 23 } 24 }
H2型号的悍马设置isAlarm()的返回值为false,也就是关闭了喇叭功能。场景类代码如代码清单10-12所示
扩展后的场景类
1 public class Client { 2 3 public static void main(String[] args) throws IOException { 4 System.out.println("-------H1型号悍马--------"); 5 System.out.println("H1型号的悍马是否需要喇叭声响?0-不需要 1-需要"); 6 String type=(new BufferedReader(new InputStreamReader(System.in))).readLine(); 7 HummerH1Model h1 = new HummerH1Model(); 8 if(type.equals("0")){ 9 h1.setAlarm(false); 10 } 11 h1.run(); 12 13 System.out.println("\n-------H2型号悍马--------"); 14 HummerH2Model h2 = new HummerH2Model(); 15 h2.run(); 16 } 17 18 }
运行是需要交互的,首先,要求输入H1型号的悍马是否有声音,如下所示:
-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要 输入“0”后的运行结果如下所示: -------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要 0 悍马H1发动... 悍马H1引擎声音是这样的... 悍马H1停车... -------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车... 输入“1”后的运行结果如下所示: -------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要 1 悍马H1发动... 悍马H1引擎声音是这样的... 悍马H1鸣笛... 悍马H1停车... -------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车...
看到没,H1型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结
果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,大家可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!
模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法,具体到前面那个例子,就是run()方法按照规定的顺序(先调用start(),然后再调用engineBoom(),再调用
alarm(),最后调用stop())调用本类的其他方法,并且由isAlarm()方法的返回值确定run()中的执行顺序变更。
5 最佳实践
初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”。这个问题很有普遍性,反正我是被问过好几回,那么父类是否可以调用子类的方法呢?我的回答是能,但强烈地、极度地不建议这么做,那该怎么做呢?
● 把子类传递到父类的有参构造中,然后调用。
● 使用反射的方式调用,你使用了反射还有谁不能调用的?!
● 父类调用子类的静态方法。
这三种都是父类直接调用子类的方法,好用不?好用!解决问题了吗?解决了!项目中允许使用不?不允许!我就一直没有搞懂为什么要用父类调用子类的方法。如果一定要调用
子类,那为什么要继承它呢?搞不懂。其实这个问题可以换个角度去理解,父类建立框架,子类在重写了父类部分的方法后,再调用从父类继承的方法,产生不同的结果(而这正是模
板方法模式)。这是不是也可以理解为父类调用了子类的方法呢?你修改了子类,影响了父类行为的结果,曲线救国的方式实现了父类依赖子类的场景,模板方法模式就是这种效果。
模板方法在一些开源框架中应用非常多,它提供了一个抽象类,然后开源框架写了一堆子类。在《××× In Action》中就说明了,如果你需要扩展功能,可以继承这个抽象类,然后
覆写protected方法,再然后就是调用一个类似execute方法,就完成你的扩展开发,非常容易扩展的一种模式。
相关推荐
《设计模式:可复用面向对象软件的基础》一书介绍了23种经典的设计模式,这些模式大致可以分为三大类: 1. **创建型模式**:专注于对象的创建机制,确保系统在合适的时机创建正确的对象。 - **单例模式**...
设计模式主要分为三大类: 1.创建型模式:工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式。 2.结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。 4.行为型模式:...
设计模式 的分类 总体来说设计模式分为三大类: 创建型模式(5): 工厂方法模式 、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式(7): 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、...
GOF(GoF)23种设计模式,是由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides四位大神在他们的著作《设计模式:可复用面向对象软件的基础》中提出的,这些模式分为创建型、结构型和行为型三大类。...
《C++ 23种设计模式1》是关于软件工程中设计模式的深入解析,主要聚焦于C++语言的实现。设计模式是经过时间和实践验证的解决方案,它们针对常见的编程问题提供了一套标准的模板,使得开发者能够更高效地编写可复用、...
JAVA 设计模式可以分为三种:创建模式、结构模式和行为模式。 1. 创建模式 创建模式是指在创建对象时使用的模式,包括 Factory(工厂模式)、Singleton(单例模式)、Builder(建造者模式)、Prototype(原型模式...
在Java编程中,有23种经典的GoF(Gang of Four)设计模式,它们被分为三大类:创建型、结构型和行为型。本资源集合了这些模式的详细解释与源码分析,旨在帮助开发者深入理解和应用设计模式。 1. 创建型模式...
本资源“Java之23种设计模式解析”源自尚硅谷教育机构,由宋红康老师主讲的“玩转Java”系列课程。这份资料详细介绍了在Java编程中常用的23种设计模式,旨在提升开发者对于软件设计的理解和应用能力,从而写出更加...
在《设计模式》课件中,详细讲解了23种经典的GOF(GoF,Gamma, Helm, Johnson, Vlissides)设计模式,这些模式分为三大类:创建型、结构型和行为型。 1. 创建型设计模式: - 单例模式:确保一个类只有一个实例,并...
本文档详细介绍了23种C#设计模式,包括创建型、结构型和行为型三个大类。这些设计模式是.NET进阶必备的知识,通过学习和掌握这些设计模式,可以提高程序员的设计和编码能力。 创建型设计模式 1. 单件模式...
《设计模式精解-GoF 23 种设计模式解析附 C++实现源码》是一本深入探讨软件设计模式的书籍,它涵盖了创建型、结构型和行为型三种主要类型的23个经典设计模式,并提供了C++语言的实现代码。设计模式是软件工程中的...
《C# 23种设计模式》是一本深入解析C#编程中常用设计模式的权威指南,对于想要提升代码质量、提高软件可维护性的开发者来说,是不可或缺的参考资料。书中详细介绍了23种经典的设计模式,这些模式是软件工程实践中...
这里我们探讨的“Java之23种设计模式完整代码”是一份宝贵的资源,它包含了所有23种经典设计模式的实现示例。这份资料能够帮助开发者深入理解每种设计模式的概念、应用场景以及它们之间的差异。 设计模式是经过时间...
标题提到的“23种面向对象设计模式”涵盖了设计模式的主要分类,这些模式在Java、C++等面向对象编程语言中广泛应用。 1. **创建型模式**(Creational Patterns):这类模式关注对象的创建过程,包括单例模式...
这本书详细阐述了23种设计模式,这些模式被广泛应用于各种编程语言,包括C++。 1. **创建型模式**:这类模式主要关注对象的创建过程,它们提供了一种在不指定具体类的情况下创建对象的方法,使得系统更加灵活和可...
### 设计模式精解——GoF 23种设计模式解析及C++实现 #### 0. 引言 设计模式作为面向对象编程的核心组成部分,是软件开发者在长期实践中总结出来的最佳实践。通过深入理解这些设计模式,我们可以更好地进行面向...
设计模式是软件开发中一种广泛采用的实践,它代表了在特定上下文中解决常见问题的通用解决方案。设计模式并非具体的代码或库,而是对最佳实践的描述,它们是经过时间验证、可重用的代码设计模式,旨在提高代码的...
java的设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享...
java23种设计模式,每一种模式都有详细的讲解,很全面,如果你想深入了解一下java23种设计模式,这会非常适合你的哦!上传乃为百度云连接,失效请留言。 内容: 001策略模式! N. B8 ~' D! f9 j+ g0 I 002观察者模式 ...
设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 ...