思来想去,还是不使用这本书了。有点脱离实践,而且实例太少,太简单,翻来看去也看不出各个设计模式的应用面究竟在哪里……于是我又搞了一本《设计模式的艺术 软件开发人员内功修炼之道》。
这一章介绍的两个设计模式都是基于继承的。
模板方法(Template Method)模式
我的手头上有个工具(tool),它可以用来做某些事情。在使用这个工具之前,我们需要找到(find)这个工具,准备(prepare)这个工具(比如把它从一堆东西里翻出来),然后使用(use)它,最后收拾(clean)它。它用程序语言怎么去描述呢?
首先,这里的工具肯定是一个抽象的概念,不存在不指代任何具体事物的所谓“工具”,所以这里的工具必定是一个抽象类。但是,我们能够确定使用它的一个流程,所以不应当使用接口(其实使用接口和 default 方法也是可行的,但是一个问题是接口不能使用 final 关键字,即使是 default 方法。)。于是,我们可以得到如下代码——
| public abstract class Tool { public void find() { System.out.println("寻找工具……"); } public abstract void prepare(); public abstract void use(); public abstract void clean(); public final void makeUseOf() { find(); prepare(); use(); clean(); } }
|
显然,这里的抽象类 Tool 承担了模板 (Template) 的作用,它规范了任何 Tool 范畴下的事物(也就是其子类)的使用流程(即 makeUseOf 方法)。子类只需要重写(实现)prepare,use,clean 方法即可。
为什么这里的 makeUseOf 是 final 方法?因为这个流程是所有 Tool 所共有的,任何 Tool 都不应该离开这个流程。而如果不使用 final,且该方法被子类重写,这会导致从概念上来说子类已经不是这个父类的种概念了。而且这个设计模式也被破坏掉了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Wrench extends Tool{ @Override public void prepare() { System.out.println("准备扳手……"); } @Override public void use() { System.out.println("使用扳手……"); } @Override public void clean() { System.out.println("整理扳手……"); } public static void main(String[] args) { new Wrench().makeUseOf(); } }
|
这种设计模式是对抽象类的一种应用。它和接口——实现的这种设计规则相区别的一个很大的不同是,它规定了其子类的某种使用流程,即使该流程所调用的方法都是抽象方法。这显然在现实生活中能找到充分的例子。比如上面说的 Tool,我们并不知道这 Tool 究竟指代的是哪个具体的工具,但是我们终归是能够归纳出所有 Tool 所共有的一些东西。
模板方法就是这样一种设计模式——抽象的父类定义处理流程的框架(这个流程中所使用的方法中应存在抽象方法),而子类则去实现该流程中的具体处理(其中的抽象方法)。
模板方法实现了所谓的反向控制,即父类调用子类的操作,通过对子类的具体实现扩展不同的行为。
下面简单提及书中的例子。例子要求实现一个 display 程序,能对一个特定类型的数据(如字符,字符串,etc……显然对于任意种类的数据都必须单独处理,比如创建其对应的类)进行格式化输出。假设在经过一些实践后,我们对任意类型的数据(对应的 display 类)都能够归纳出一定的流程——首先创建开头,再创建五次正文(为什么是五次?问甲方去),再创建结尾。于是,我们可以应用模板方法进行这样的抽象——
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| public abstract class AbstractDisplay { protected abstract void open(); protected abstract void print(); protected abstract void close(); public final void display() { open(); for (int i = 0; i < 5; i++) print(); close(); } }
public class CharDisplay extends AbstractDisplay{ private char ch; public CharDisplay(char ch) { this.ch = ch; } @Override protected void open() { System.out.print("<<"); } @Override protected void print() { System.out.print(ch); } @Override protected void close() { System.out.println(">>"); } }
public class StringDisplay extends AbstractDisplay { private String str; private int width; public StringDisplay(String str) { this.str = str; this.width = str.getBytes().length; } private void printLine() { System.out.print("+"); for (int i = 0; i < width; i++) System.out.print("-"); System.out.print("+"); } @Override protected void open(){ printLine(); } @Override protected void print(){ System.out.println("|"+str+"|"); } @Override protected void close(){ printLine(); } }
|
Main 文件省略。使用了模板方法的好处是,如果要修改处理的流程,比如要改成 print10 次,只需要更改抽象父类即可。如果不对这流程进行抽象,每次要更改需求就需要更改所有实现类了。
模板方法的一个问题是,子类必须要了解父类中所定义的流程,也就是说要理解父类中定义的抽象方法的调用时机,否则子类的编写是比较困难的。这说明父类和子类是紧密联系的(紧耦合?)。