java设计模式

设计模式公司荣誉出品
您的设计模式
我们的设计模式
CBF4LIFE
2009 年 年 5 月
我希望这本书的阅读者具备最基本的代码编写能力,您是一个初级的 coder,可以从中领会到怎么设计
一段优秀的代码;您是一个高级程序员,可以从中全面了解到设计模式以及 Java 的边角技术的使用;您是
一个顶级的系统分析师,可以从中获得共鸣,寻找到项目公共问题的解决办法,呀,是不是把牛吹大了?!
您的设计模式
目    录
第 1 章  策略模式【STRATEGY PATTERN 】 …………………………………………………………………………………….. 4
第 2 章  代理模式【PROXY PATTERN 】 …………………………………………………………………………………………. 8
第 3 章  单例模式【SINGLETON PATTERN 】 …………………………………………………………………………………..12
第 4 章  多例模式【MULTITION PATTERN 】 …………………………………………………………………………………..16
第 5 章  工厂方法模式【FACTORY METHOD PATTERN 】 ………………………………………………………………….19
第 6 章  抽象工厂模式【ABSTRACT FACTORY PATTERN 】 ………………………………………………………………..31
第 7 章  门面模式【FACADE PATTERN 】 ………………………………………………………………………………………. 44
第 8 章  适配器模式【ADAPTER PATTERN 】 …………………………………………………………………………………. 51
第 9 章  模板方法模式【TEMPLATE METHOD PATTERN 】 ………………………………………………………………..63
第 10 章 建造者模式【BUILDER PATTERN 】 ……………………………………………………………………………………82
第 11 章 桥梁模式【BRIDGE PATTERN 】 ……………………………………………………………………………………….. 97
第 12 章 命令模式【COMMAND PATTERN 】 ………………………………………………………………………………… 112
第 13 章 装饰模式【DECORATOR PATTERN 】 ……………………………………………………………………………….. 126
第 14 章 迭代器模式【ITERATOR PATTERN 】 ……………………………………………………………………………….. 137
第 15 章 组合模式【COMPOSITE PATTERN 】 ……………………………………………………………………………….. 147
第 16 章 观察者模式【OBSERVER PATTERN 】 ………………………………………………………………………………. 175
第 17 章 责任链模式【CHAIN OF RESPONSIBILITY PATTERN 】 ………………………………………………………… 194
第 18 章 访问者模式【VISITOR PATTERN 】 ………………………………………………………………………………….. 210
第 19 章 状态模式【STATE PATTERN 】 ………………………………………………………………………………………… 236
第 20 章 原型模式【PROTOTYPE PATTERN 】 ……………………………………………………………………………….. 255
第 21 章 中介者模式【MEDIATOR PATTERN 】 ……………………………………………………………………………… 256
第 22 章 解释器模式【INTERPRETER PATTERN 】 ………………………………………………………………………….. 257
第 23 章 亨元模式【FLYWEIGHT PATTERN 】 ………………………………………………………………………………… 258
第 24 章 备忘录模式【MEMENTO PATTERN 】 ……………………………………………………………………………… 259
第 25 章 模式大PK……………………………………………………………………………………………………………………. 260
第 26 章 六大设计原则 …………………………………………………………………………………………………………….. 261
26.1  单一职责原则【S INGLE  R ESPONSIBILITY  P RINCIPLE 】 ……………………………………………………………………….. 261
26.2  里氏替换原则【L ISKOV  S UBSTITUTION  P RINCIPLE 】 …………………………………………………………………………. 268
第  2  页
您的设计模式
26.3  依赖倒置原则【D EPENDENCE  I NVERSION  P RINCIPLE 】 ……………………………………………………………………… 280
26.4  接口隔离原则【I NTERFACE  S EGREGATION  P RINCIPLE 】 ……………………………………………………………………… 281
26.5  迪米特法则【L OW  O F  D EMETER 】 ……………………………………………………………………………………………… 282
26.6  开闭原则【O PEN  C LOSE  P RINCIPLE 】……………………………………………………………………………………………. 292
第 27 章 混编模式讲解 …………………………………………………………………………………………………………….. 294
第 28 章 更新记录: ………………………………………………………………………………………………………………… 296
相关说明 ……………………………………………………………………………………………………………………………………. 297
相关说明 ……………………………………………………………………………………………………………………………………. 297
第 29 章 后序 ………………………………………………………………………………………………………………………….. 298
第  3  页
您的设计模式
第 1  章 策略模式【Strategy Pattern 】
刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手问题,
嘿,还别说,真是解决了大问题,搞到最后是周瑜陪了夫人又折兵呀,那咱们先看看这个场景是什么样子
的。
先说这个场景中的要素:三个妙计,一个锦囊,一个赵云,妙计是小亮同志给的,妙计是放置在锦囊
里,俗称就是锦囊妙计嘛,那赵云就是一个干活的人,从锦囊中取出妙计,执行,然后获胜,用 JAVA 程序
怎么表现这个呢?我们先看类图:
三个妙计是同一类型的东东,那咱就写个接口:
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 首先定一个策略接口,这是诸葛亮老人家给赵云的三个锦囊妙计的接口
*
*/
public interface IStrategy {
//每个锦囊妙计都是一个可执行的算法
public void operate();
}
第  4  页
您的设计模式
然后再写三个实现类,有三个妙计嘛:
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 找乔国老帮忙,使孙权不能杀刘备
*/
public class BackDoor implements IStrategy {
public void operate() {
System.out.println(“找乔国老帮忙,让吴国太给孙权施加压力”);
}
}
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 求吴国太开个绿灯
*/
public class GivenGreenLight implements IStrategy {
public void operate() {
System.out.println(“求吴国太开个绿灯,放行!”);
}
}
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 孙夫人断后,挡住追兵
*/
public class BlockEnemy implements IStrategy {
public void operate() {
第  5  页
您的设计模式
System.out.println(“孙夫人断后,挡住追兵”);
}
}
好了,大家看看,三个妙计是有了,那需要有个地方放这些妙计呀,放锦囊呀:
 
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 计谋有了,那还要有锦囊
*/
public class Context {
//构造函数,你要使用那个妙计
private IStrategy straegy;
public Context(IStrategy strategy){
this.straegy = strategy;
}
//使用计谋了,看我出招了
public void operate(){
this.straegy.operate();
}
}
然后就是赵云雄赳赳的揣着三个锦囊,拉着已步入老年行列的、还想着娶纯情少女的、色迷迷的刘老
爷子去入赘了,嗨,还别说,小亮的三个妙计还真是不错,瞅瞅:
package com.cbf4life.strategy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class ZhaoYun {
/**
* 赵云出场了,他根据诸葛亮给他的交代,依次拆开妙计
*/
public static void main(String[] args) {
Context context;
第  6  页
您的设计模式
//刚刚到吴国的时候拆第一个
System.out.println(“———–刚刚到吴国的时候拆第一个————-“);
context = new Context(new BackDoor()); //拿到妙计
context.operate(); //拆开执行
System.out.println(“\n\n\n\n\n\n\n\n”);
//刘备乐不思蜀了,拆第二个了
System.out.println(“———–刘备乐不思蜀了,拆第二个了————-“);
context = new Context(new GivenGreenLight());
context.operate(); //执行了第二个锦囊了
System.out.println(“\n\n\n\n\n\n\n\n”);
//孙权的小兵追了,咋办?拆第三个
System.out.println(“———–孙权的小兵追了,咋办?拆第三个
————-“);
context = new Context(new BlockEnemy());
context.operate(); //孙夫人退兵
System.out.println(“\n\n\n\n\n\n\n\n”);
/*
*问题来了:赵云实际不知道是那个策略呀,他只知道拆第一个锦囊,
*而不知道是BackDoor这个妙计,咋办? 似乎这个策略模式已经把计谋名称写出来了
*
* 错! BackDoor、 GivenGreenLight、 BlockEnemy只是一个代码, 你写成first、 second、
third,没人会说你错!
*
* 策略模式的好处就是:体现了高内聚低耦合的特性呀,缺点嘛,这个那个,我回去再查查
*/
}
}
就这三招,搞的周郎是“陪了夫人又折兵”呀!这就是策略模式,高内聚低耦合的特点也表现出来了,
还有一个就是扩展性,也就是 OCP 原则,策略类可以继续增加下去,只要修改 Context.java 就可以了,这
个不多说了,自己领会吧。
第  7  页
您的设计模式
第 2  章 代理模式【Proxy Pattern 】
什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道
被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被
代理的人能干活呀。
比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理,表现在程序上时这样的:
先定义一种类型的女人:
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一种类型的女人,王婆和潘金莲都属于这个类型的女人
*/
public interface KindWomen {
//这种类型的女人能做什么事情呢?
public void makeEyesWithMan(); //抛媚眼
public void happyWithMan(); //happy what? You know that!
}
一种类型嘛,那肯定是接口,然后定义潘金莲:
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定一个潘金莲是什么样的人
*/
public class PanJinLian implements KindWomen {
public void happyWithMan() {
System.out.println(“潘金莲在和男人做那个…..”);
}
第  8  页
您的设计模式
public void makeEyesWithMan() {
System.out.println(“潘金莲抛媚眼”);
}
}
再定一个丑陋的王婆:
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 王婆这个人老聪明了,她太老了,是个男人都看不上,
* 但是她有智慧有经验呀,她作为一类女人的代理!
*/
public class WangPo implements KindWomen {
private KindWomen kindWomen;
public WangPo(){ //默认的话,是潘金莲的代理
this.kindWomen = new PanJinLian();
}
//她可以是KindWomen的任何一个女人的代理,只要你是这一类型
public WangPo(KindWomen kindWomen){
this.kindWomen = kindWomen;
}
public void happyWithMan() {
this.kindWomen.happyWithMan(); //自己老了,干不了,可以让年轻的代替
}
public void makeEyesWithMan() {
this.kindWomen.makeEyesWithMan(); //王婆这么大年龄了,谁看她抛媚眼?!
}
}
两个女主角都上场了,男主角也该出现了:
第  9  页
您的设计模式
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一个西门庆,这人色中饿鬼
*/
public class XiMenQing {
/*
* 水浒里是这样写的:西门庆被潘金莲用竹竿敲了一下难道,痴迷了,
* 被王婆看到了, 就开始撮合两人好事,王婆作为潘金莲的代理人
* 收了不少好处费,那我们假设一下:
* 如果没有王婆在中间牵线,这两个不要脸的能成吗?难说的很!
*/
public static void main(String[] args) {
//把王婆叫出来
WangPo wangPo = new WangPo();
//然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆丢筷子的那出戏:
wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上爽的是潘金莲
wangPo.happyWithMan(); }
}
那这就是活生生的一个例子,通过代理人实现了某种目的,如果真去掉王婆这个中间环节,直接是西
门庆和潘金莲勾搭,估计很难成就武松杀嫂事件。
那我们再考虑一下,水浒里还有没有这类型的女人?有,卢俊义的老婆贾氏(就是和那个固管家苟合
的那个) ,这名字起的:“假使” ,那我们也让王婆做她的代理:
把贾氏素描出来:
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class JiaShi implements KindWomen {
public void happyWithMan() {
System.out.println(“贾氏正在Happy中……”);
第  10  页
您的设计模式
}
public void makeEyesWithMan() {
System.out.println(“贾氏抛媚眼”);
}
}
西门庆勾贾氏:
package com.cbf4life.proxy;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一个西门庆,这人色中饿鬼
*/
public class XiMenQing {
public static void main(String[] args) {
//改编一下历史,贾氏被西门庆勾走:
JiaShi jiaShi = new JiaShi();
WangPo wangPo = new WangPo(jiaShi); //让王婆作为贾氏的代理人
wangPo.makeEyesWithMan();
wangPo.happyWithMan();
}
}
说完这个故事,那额总结一下,代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是
接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,
大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗。
第  11  页
您的设计模式
第 3  章 单例模式【Singleton Pattern 】
这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,
单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?
我们举个比较难复制的对象:皇帝
中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这
个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过
渡期的不考虑,别找茬哦) ,大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,单例模式,绝对的单例模式,
先看类图:
然后我们看程序实现,先定一个皇帝:
package com.cbf4life.singleton1;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 中国的历史上一般都是一个朝代一个皇帝,有两个皇帝的话,必然要PK出一个皇帝出来
*/
public class Emperor {
private static Emperor emperor = null; //定义一个皇帝放在那里,然后给这个皇帝名字
private Emperor(){
//世俗和道德约束你,目的就是不让你产生第二个皇帝
}
public static Emperor getInstance(){
第  12  页
您的设计模式
if(emperor == null){ //如果皇帝还没有定义,那就定一个
emperor = new Emperor();
}
return emperor;
}
//皇帝叫什么名字呀
public static void emperorInfo(){
System.out.println(“我就是皇帝某某某….”);
}
}
然后定义大臣:
package com.cbf4life.singleton1;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 大臣是天天要面见皇帝,今天见的皇帝和昨天的,前天不一样那就出问题了!
*/
@SuppressWarnings(“all”)
public class Minister {
/**
* @param args
*/
public static void main(String[] args) {
//第一天
Emperor emperor1=Emperor.getInstance();
emperor1.emperorInfo(); //第一天见的皇帝叫什么名字呢?
//第二天
Emperor emperor2=Emperor.getInstance();
Emperor.emperorInfo();
//第三天
Emperor emperor3=Emperor.getInstance();
emperor2.emperorInfo();
//三天见的皇帝都是同一个人,荣幸吧!
}
}
第  13  页
您的设计模式
看到没,大臣天天见到的都是同一个皇帝,不会产生错乱情况,反正都是一个皇帝,是好是坏就这一
个,只要提到皇帝,大家都知道指的是谁,清晰,而又明确。问题是这是通常情况,还有个例的,如同一
个时期同一个朝代有两个皇帝,怎么办?
单例模式很简单,就是在构造函数中多了加一个构造函数,访问权限是 private 的就可以了,这个模
式是简单,但是简单中透着风险,风险?什么风险?在一个 B/S 项目中,每个 HTTP Request 请求到 J2EE
的容器上后都创建了一个线程,每个线程都要创建同一个单例对象,怎么办?,好,我们写一个通用的单例程
序,然后分析一下:
package com.cbf4life.singleton3;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 通用单例模式
*/
@SuppressWarnings(“all”)
public class SingletonPattern {
private static SingletonPattern singletonPattern= null;
// 限制住不能直接产生一个实例
private SingletonPattern(){
}
public SingletonPattern getInstance(){
if(this.singletonPattern == null){ // 如果还没有实例,则创建一个
this.singletonPattern = new SingletonPattern();
}
return this.singletonPattern;
}
}
我们来看黄色的那一部分,假如现在有两个线程 A 和线程 B,线程 A 执行到 this.singletonPattern =
new SingletonPattern(),正在申请内存分配,可能需要 0.001 微秒,就在这 0.001 微秒之内,线程 B 执
行到 if(this.singletonPattern == null),你说这个时候这个判断条件是 true 还是 false?是 true,那
然后呢?线程 B 也往下走, 于是乎就在内存中就有两个 SingletonPattern 的实例了, 看看是不是出问题了?
第  14  页
您的设计模式
如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!数据一致性
校验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上你是重现不
了的,不寒而栗吧,那怎么修改?有很多种方案,我就说一种,能简单的、彻底解决问题的方案:
package com.cbf4life.singleton3;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 通用单例模式
*/
@SuppressWarnings(“all”)
public class SingletonPattern {
private static final SingletonPattern singletonPattern= new
SingletonPattern();
// 限制住不能直接产生一个实例
private SingletonPattern(){
}
public synchronized static SingletonPattern getInstance(){
return singletonPattern;
}
}
直接 new 一个对象传递给类的成员变量 singletonpattern,你要的时候 getInstance()直接返回给
你,解决问题!
第  15  页
您的设计模式
第 4  章 多例模式【Multition Pattern 】
这种情况有没有?有!大点声,有没有? 有! ,是,确实有,就出现在明朝,那三国期间的算不算,
不算,各自称帝,各有各的地盘,国号不同。大家还记得那首诗《石灰吟》吗?作者是谁?于谦,他是被
谁杀死的?明英宗朱祁镇,对,就是那个在土木堡之变中被瓦刺俘虏的皇帝,被俘虏后,他弟弟朱祁钰当
上了皇帝,就是明景帝,估计当上皇帝后乐疯了,忘记把老哥朱祁镇削为太上皇了,我 Shit,在中国的历
史上就这个时期是有 2个皇帝,你说这期间的大臣多郁闷,两个皇帝耶,两个精神依附对象呀。
这个场景放到我们设计模式中就是叫有上限的多例模式 (没上限的多例模式太容易了, 和你直接 new 一
个对象没啥差别,不讨论)怎么实现呢,看我出招,先看类图:
然后看程序,先把两个皇帝定义出来:
package com.cbf4life.singleton2;
import java.util.ArrayList;
import java.util.Random;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 中国的历史上一般都是一个朝代一个皇帝,有两个皇帝的话,必然要PK出一个皇帝出来。
* 问题出来了:如果真在一个时间,中国出现了两个皇帝怎么办?比如明朝土木堡之变后,
* 明英宗被俘虏,明景帝即位,但是明景帝当上皇帝后乐疯了,竟然忘记把他老哥明英宗削为太上皇,
* 也就是在这一个多月的时间内,中国竟然有两个皇帝!
*
*/
第  16  页
您的设计模式
@SuppressWarnings(“all”)
public class Emperor {
private static int maxNumOfEmperor = 2; //最多只能有连个皇帝
private static ArrayList emperorInfoList=new ArrayList(maxNumOfEmperor); //
皇帝叫什么名字
private static ArrayList emperorList=new ArrayList(maxNumOfEmperor); //装皇
帝的列表;
private static int countNumOfEmperor =0; //正在被人尊称的是那个皇帝
//先把2个皇帝产生出来
static{
//把所有的皇帝都产生出来
for(int i=0;i

protected abstract String getOtherInfo()

CommonEmployee
+public String getJob()
+public void setJob(String job)

protected String getOtherInfo()

Manager
+public String getPerformance()
+public void setPerformance(String performance)

protected String getOtherInfo()()

Client
这个类图还是比较简单的,使用了一个模版方法模式,把所要的信息都打印出来,我们先来看一下抽
象类:
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 在一个单位里谁都是员工,甭管你是部门经理还是小兵
第  210  页
您的设计模式
*/
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
//甭管是谁,都有工资
private String name;
//只要是员工那就有薪水
private int salary;
//性别很重要
private int sex;
//以下是简单的getter/setter,不多说
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//打印出员工的信息
public final void report(){
String info = “姓名:” + this.name + “\t”;
info = info + “性别:” + (this.sex == FEMALE?”女”:”男”) + “\t”;
info = info + “薪水:” + this.salary + “\t”;
第  211  页
您的设计模式
//获得员工的其他信息
info = info + this.getOtherInfo();
System.out.println(info);
}
//拼装员工的其他信息
protected abstract String getOtherInfo();
}
再看小兵的实现类,越卑微的人物越能引起共鸣,因为我们有共同的经历、思维和苦难,呵呵,看实
现类:
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 普通员工,也就是最小的小兵
*/
public class CommonEmployee extends Employee {
//工作内容,这个非常重要,以后的职业规划就是靠这个了
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
protected String getOtherInfo(){
return “工作:”+ this.job + “\t”;
}
}
在来看领导阶层:
第  212  页
您的设计模式
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 经理级人物
*/
public class Manager extends Employee {
//这类人物的职责非常明确:业绩
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
protected String getOtherInfo(){
return “业绩:”+ this.performance + “\t”;
}
}
Performance 这个单词在我们技术人员的眼里就是代表性能,在实际商务英语中可以有 Sales
Performance 销售业绩,performance evaluation 业绩评估等等。然后我们来看一下我们的 invoker 类:
package com.cbf4life.common;
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Client {
public static void main(String[] args) {
for(Employee emp:mockEmployee()){
第  213  页
您的设计模式
emp.report();
}
}
//模拟出公司的人员情况,我们可以想象这个数据室通过持久层传递过来的
public static List mockEmployee(){
List empList = new ArrayList();
//产生张三这个员工
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob(“编写Java程序,绝对的蓝领、苦工加搬运工”);
zhangSan.setName(“张三”);
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
//产生李四这个员工
CommonEmployee liSi = new CommonEmployee();
liSi.setJob(“页面美工,审美素质太不流行了!”);
liSi.setName(“李四”);
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
//再产生一个经理
Manager wangWu = new Manager();
wangWu.setName(“王五”);
wangWu.setPerformance(“基本上是负值,但是我会拍马屁呀”);
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
}
}
先通过 mockEmployee 来模拟出一个数组,当然了在实际项目中这个数组应该是从持久层产生过来的。
我们来看运行结果:
姓名:张三 性别:男 薪水:1800 工作:编写Java程序,绝对的蓝领、苦工加搬运工
姓名:李四 性别:女 薪水:1900 工作:页面美工,审美素质太不流行了!
姓名:王五 性别:男 薪水:18750 业绩:基本上是负值,但是我会拍马屁呀
第  214  页
您的设计模式
结果出来了,也非常正确。我们来想一想这个实际的情况,人力资源部门拿这这份表格会给谁看呢?
那当然是大老板了,大老板关心的是什么?关心部门经理的业绩!小兵的情况不是他要了解的,就像二战
的时候一位将军(巴顿?艾森豪威尔?记不清楚了)说的“我一想到我的士兵也有孩子、妻子、父母,我
就痛心疾首,…但是这是战场,我只能认为他们是一群机器…”,是呀,其实我们也一样呀,那问题就出
来了:
? 大老板就看部门经理的报表,小兵的报表可看可不看;
? 多个大老板, “嗜好”是不同的,主管销售的,则主要关心的营销情况;主管会计的,则主要关心
企业的整体财务运行状态;主管技术的,则主要看技术的研发情况;
综合成一句话,这个报表会有修改:数据的修改以及报表的展现修改,按照开闭原则,项目分析的时
候已经考虑到这些可能引起变更的隐私,就需要在设计时考虑通过扩展来来避开未来需求变更而引起的代
码修改风险。我们来想一想,每个普通员工类和经理类都一个方法 report,那是否可以把这个方法提取到
另外一个类中来实现呢?原有的示意图如下:
CommonEmployee
+public void report()
Manager
+public void report()
Employee
这两个类都一个相同的方法 report(),但是要实现的内容不相同,而且还有可能会发生变动,那我们
就让其他类来实现这个 report 方法,好,看示意图图的变更:
第  215  页
您的设计模式
CommonEmployee
Manager
Employee
Visitor
+public void report()
两个类的 report 方法都不需要了,只有 Visitor 类来实现了 report 的方法,这个猛一看还真有点委
托(intergration)的意味,我们实现下来你就知道这和委托有非常大的差距,我们来看类图:
第  216  页
您的设计模式
Employee
+public final static int MALE = 0
+public final static int FEMALE = 1
+public String getName()
+public void setName(String name)
+public int getSalary()
+public void setSalary(int salary)
+public int getSex()
+public void setSex(int sex)
+public final void report()
+public abstract void accept(IVisitor visitor)
Client
CommonEmployee
+public String getJob()
+public void setJob(String job)
+public void accept(IVisitor visitor)
Manager
+public String getPerformance()
+public void setPerformance(String performance)
+public void accept(IVisitor visitor)
IVisitor
<>
+public void visit(CommonEmployee commonEmployee)
+public void visit(Manager manager)
Visitor
定义一个访问者
accept 定义那个访问者能来访问
在抽象类 Employee 中增加了 accept 方法,这个方法是定义我这个类可以允许被谁来访问,也就定义
一类访问者,在具体的实现类中调用访问者的方法。我们先看访问者接口 IVisitor 程序:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 访问者,我要去访问人家的数据了
*/
public interface IVisitor {
第  217  页
您的设计模式
//首先定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
//其次定义,我还可以访问部门经理
public void visit(Manager manager);
}
如下是访问者的实现类:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Visitor implements IVisitor {
//访问普通员工,打印出报表
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
}
//访问部门经理,打印出报表
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}
//组装出基本信息
private String getBasicInfo(Employee employee){
String info = “姓名:” + employee.getName() + “\t”;
info = info + “性别:” + (employee.getSex() == Employee.FEMALE?”女”:”男”)
+ “\t”;
info = info + “薪水:” + employee.getSalary() + “\t”;
return info;
}
//组装出部门经理的信息
private String getManagerInfo(Manager manager){
String basicInfo = this.getBasicInfo(manager);
String otherInfo = “业绩:”+manager.getPerformance() + “\t”;
第  218  页
您的设计模式
return basicInfo + otherInfo;
}
//组装出普通员工信息
private String getCommonEmployee(CommonEmployee commonEmployee){
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = “工作:”+commonEmployee.getJob()+”\t”;
return basicInfo + otherInfo;
}
}
在具体的实现类中,定义了两个私有方法,作用就是产生需要打印的数据和格式,然后在访问者访问
相关的对象是,产生这个报表。继续看 Employee 抽象类:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 在一个单位里谁都是员工,甭管你是部门经理还是小兵
*/
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
//甭管是谁,都有工资
private String name;
//只要是员工那就有薪水
private int salary;
//性别很重要
private int sex;
//以下是简单的getter/setter,不多说
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
第  219  页
您的设计模式
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//我允许一个访问者过来访问
public abstract void accept(IVisitor visitor);
}
删除了 report 方法,增加了 accept 方法,需要实现类来实现。继续看实现类:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 普通员工,也就是最小的小兵
*/
public class CommonEmployee extends Employee {
//工作内容,这个非常重要,以后的职业规划就是靠这个了
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
//我允许访问者过来访问
第  220  页
您的设计模式
@Override
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
上面是普通员工的实现类,这个实现类的 accept 方法很简单,这个类就把自身传递过去,也就是让访
问者访问本身这个对象。再看 Manager 类:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 经理级人物
*/
public class Manager extends Employee {
//这类人物的职责非常明确:业绩
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
//部门经理允许访问者访问
@Override
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
所有的业务定义都已经完成,我们来看看怎么调用这个逻辑:
package com.cbf4life.common2;
第  221  页
您的设计模式
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Client {
public static void main(String[] args) {
for(Employee emp:mockEmployee()){
emp.accept(new Visitor());
}
}
//模拟出公司的人员情况,我们可以想象这个数据室通过持久层传递过来的
public static List mockEmployee(){
List empList = new ArrayList();
//产生张三这个员工
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob(“编写Java程序,绝对的蓝领、苦工加搬运工”);
zhangSan.setName(“张三”);
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
//产生李四这个员工
CommonEmployee liSi = new CommonEmployee();
liSi.setJob(“页面美工,审美素质太不流行了!”);
liSi.setName(“李四”);
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
//再产生一个经理
Manager wangWu = new Manager();
wangWu.setName(“王五”);
wangWu.setPerformance(“基本上是负值,但是我会拍马屁呀”);
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
第  222  页
您的设计模式
}
}
改动非常少,就黄色那么一行的改动,我们看运行结果:
姓名:张三 性别:男 薪水:1800 工作:编写Java程序,绝对的蓝领、苦工加搬运工
姓名:李四 性别:女 薪水:1900 工作:页面美工,审美素质太不流行了!
姓名:王五 性别:男 薪水:18750 业绩:基本上是负值,但是我会拍马屁呀
运行结果也完全相同,那回过头我们来看看这个程序是怎么实现的:
? 首先通过循环遍历所有元素;
? 其次,每个员工对象都定义了一个访问者;
? 再其次,员工对象把自己做为一个参数调用访问者 visit 方法;
? 然后,访问者调用自己内部的计算逻辑,计算出相应的数据和表格元素;
? 最后,访问者打印出报表和数据;
事情的经过就是这个样子滴~,那我们再来看看上面提到的数据和报表格式都会改变的情况,首先数据
的改变,数据改那当然都要改,这个没跑,说不上两个方案有什么优劣;其次报表格式修改,这个方案绝
对是有优势的,你看我只要再产生一个 Visitor 就可以产生一个新的报表格式,而其他的类都不用修改,
再如果你是用 Spring 开发的话,那就更爽了,在 Spring 的配置文件中使用的是接口注入,我只要把配置
文件中的 ref 修改一下就成,别的什么都不用修改了!
以上讲的就是访问者模式,这个模式的通用类图如下:
第  223  页
您的设计模式
Visitor
+VisitConcreateElement(elem: ConcreateElement)
Element
+Accept(v: Visitor)
ConcreteVisitor
ConcreateElement
ObjectStruture
*
Client
看了这个通用类图,大家可能要犯迷糊了,这里怎么有一个 ObjectStruture 这个类呢?你刚刚举得例
子就没有呢?真没有嘛?我们不是定义了一个 List 了吗?这就是一个 ObjectStruture, 我们来看这几个角
色的职责:
抽象访问者(Visitor) :抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是 visit
方法的参数定义哪些对象是可以被访问的;
具体访问者(ConcreteVisitor):访问者访问到一个类后该怎么干(哎,这个别读歪了),要做什么事
情;
抽象元素(Element) :接口或者抽象类,声明接受那一类型的访问者访问,程序上是通过 accept 方法
中的参数来定义;
具体元素: (ConcreteElement):实现 accept 方法,通常是 visitor.visit(this),基本上都形成了一
个套路了;
结构对象(ObjectStruture):容纳多个不同类、不同接口的容器,比如 List、Set、Map 等,在项目中,
一般很少抽象出来这个角色;
大家可以这样理解访问者模式,我作为一个访客(Visitor)到朋友家(Visited Class)去拜访,朋
友之间聊聊天,喝喝酒,再相互吹捧吹捧,炫耀炫耀,这都正常,聊天的时候,朋友告诉我,他今年加官
进爵了,工资也涨了 30%,准备再买套房子,那我就在心里盘算(Visitor-self-method) “你个龟儿子,这
么有钱,老子去年要借 10W 你都不借” ,我根据被朋友的信息,执行了自己的一个方法。
接下来我们来思考一下,访问者可以用在什么地方。在这种地方你一定要考虑到使用访问者模式:业
第  224  页
您的设计模式
务规则要求遍历多个不同的对象。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数
据, (当然了,你使用 instanceof 的话,能访问所有的数据,这个不争论) ,而访问者模式是对迭代器模式
的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。访
问者模式还有一个用途,就是充当拦截器(Interceptor)角色,这个我们在后边来讲。
访问者模式有哪些优点呢?首先是符合单一职责原则,具体元素角色也就是 Employee 这个类的两个子
类负责数据的加载,而 Visitor 类则负责报表的展现,两个不同的职责非常明确的分离开来,各自演绎而
变化;其次,由于职责分开,继续增加对数据的操作是非常快捷的,例如现在要增加一个给最大老板的一
份报表,这份报表格式又有所不同,容易处理吧,直接在 Visitor 中增加一个方法,传递过来数据后进行
整理打印;最后,数据汇总,就以刚刚我们说的 Employee 的例子,如果我现在要统计所有员工的工资之和,
怎么计算?把所有人的工资 for 循环加一遍?是个办法,那我再提个问题,员工工资*1.2,部门经理*1.4,
总经理*1.8,然后把这些工资加起来,你怎么处理?1.2,1.4,1.8 是什么?我 K,你没看到领导不论什么
时候都比你拿的多,工资奖金就不说了,就是过节发个慰问劵也比你多,就是这个系数在作祟。我们继续
说你先怎么统计?使用 for 循环,然后使用 instanceof 来判断是员工还是经理?可以解决,但不是个好办
法,好办法是通过访问者模式来实现,把数据扔给访问者,由访问者来进行统计计算。
访问者模式的缺点也很明显,访问者要访问一个类就必然要求这个类公布一些方法,也就是说访问者
关注了其他类的内部细节,这是迪米特法则所不建议的;还有一个缺点就是,具体角色的增加删除修改都
是比较苦难的,就上面那个例子,你想想,你要是想增加一个成员变量,比如年龄 age,Visitor 就需要修
改,如果 Visitor 是一个还好说,多个呢?业务逻辑再复杂点呢?访问者模式是有缺点的,是事物都有缺
点,但是这仍然掩盖不了它的光芒,访问者模式结合其他模式比如模版方法模式、状态模式、解释器模式、
代理模式等就会非常强大,这个我们放在模式混编中来讲解。
访问者模式是会经常用到的模式,虽然你不注意,有可能你起的名字也不是什么 Visitor,但是这个是
非常容易使用到的,在这里我提出三个扩展的功能共大家参考。
统计功能。在访问者模式中的使用中我也提到访问者的统计功能,汇总和报表是金融类企业非常常用
的功能,基本上都是一堆的计算公式,然后出一个报表,很多项目是采用了数据库的存储过程来实现,这
个我不是很推荐,除非海量数据处理,一个晚上要上亿、几十亿条的数据跑批处理,这个除了存储过程来
处理没有其他办法的,你要是用应用服务器来处理,连接数据库的网络就是处于 100%占用状态,一个晚上
也未必跑得完这批数据!除了这种海量数据外,我建议数据统计和报表的批处理通过访问者模式来处理会
比较简单。好,那我们来统计一下公司人员的工资,先看类图:
第  225  页
您的设计模式
就在接口上增加了一个 getTotalSalary 方法,在 Visitor 实现类中实现该方法,我们先看接口:
package com.cbf4life.extend;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 访问者,我要去访问人家的数据了
*/
public interface IVisitor {
// 首先定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
// 其次定义,我还可以访问部门经理
public void visit(Manager manager);
第  226  页
您的设计模式
// 统计所有员工工资总和
public int getTotalSalary();
}
就多了一个 getTotalSalary 方法,我们再来看实现类:
package com.cbf4life.extend;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Visitor implements IVisitor {
// 部门经理的工资系数是 5
private final static int MANAGER_COEFFICIENT = 5;
// 员工的工资系数是 2
private final static int COMMONEMPLOYEE_COEFFICIENT = 2;
// 普通员工的工资总和
private int commonTotalSalary = 0;
// 部门经理的工资总和
private int managerTotalSalary =0;
// 访问普通员工,打印出报表
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
// 计算普通员工的薪水总和
this.calCommonSlary(commonEmployee.getSalary());
}
// 访问部门经理,打印出报表
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
// 计算部门经理的工资总和
this.calManagerSalary(manager.getSalary());
}
// 组装出基本信息
private String getBasicInfo(Employee employee){
String info = ” 姓名: ” + employee.getName() + “\t”;
info = info + ” 性别: ” + (employee.getSex() == Employee.FEMALE?” 女 “:” 男 “)
第  227  页
您的设计模式
+ “\t”;
info = info + ” 薪水: ” + employee.getSalary() + “\t”;
return info;
}
// 组装出部门经理的信息
private String getManagerInfo(Manager manager){
String basicInfo = this.getBasicInfo(manager);
String otherInfo = ” 业绩: “+manager.getPerformance() + “\t”;
return basicInfo + otherInfo;
}
// 组装出普通员工信息
private String getCommonEmployee(CommonEmployee commonEmployee){
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = ” 工作: “+commonEmployee.getJob()+”\t”;
return basicInfo + otherInfo;
}
// 计算部门经理的工资总和
private void calManagerSalary(int salary){
this.managerTotalSalary = this.managerTotalSalary + salary
*MANAGER_COEFFICIENT ;
}
// 计算普通员工的工资总和
private void calCommonSlary(int salary){
this.commonTotalSalary = this.commonTotalSalary +
salary*COMMONEMPLOYEE_COEFFICIENT;
}
// 获得所有员工的工资总和
public int getTotalSalary(){
return this.commonTotalSalary + this.managerTotalSalary;
}
}
程序比较长,但是还是比较简单的,分别计算普通员工和经理级员工的工资总和,然后加起来。注意
我们在实现时已经考虑员工工资和经理工资的系数不同。
我们再来看 Client 类的调用:
第  228  页
您的设计模式
package com.cbf4life.extend;
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Client {
public static void main(String[] args) {
IVisitor visitor = new Visitor();
for(Employee emp:mockEmployee()){
emp.accept(visitor);
}
System.out.println(” 本公司的月工资总额是: “+visitor.getTotalSalary());
}
}
其中 mockEmployee 静态方法没有任何改动,就没有拷贝上去。我们来看运行的结果:
姓名:张三 性别:男 薪水: 1800 工作:编写 Java 程序,绝对的蓝领、苦工加搬运工
姓名:李四 性别:女 薪水: 1900 工作:页面美工,审美素质太不流行了!
姓名:王五 性别:男 薪水: 18750 业绩:基本上是负值,但是我会拍马屁呀
本公司的月工资总额是: 101150
然后你想修改工资的系数,没有问题!想换个展示格式,也没有问题!自己练习一下吧
多个访问者。在实际的项目中,一个对象,多个访问者的情况非常多。其实我们上面例子就应该是两
个访问者,为什么呢?报表分两种,一种是展示表,通过数据库查询,把结果展示出来,这个就类似于我
们的那个列表;第二种是汇总表,这个是需要通过模型或者公式计算出来的,一般都是批处理结果,这个
类似于我们计算工资总额,这两种报表格式是对同一堆数据的两种处理方式,从程序上看,一个类就有个
不同的访问者了,那我们修改一下类图:
第  229  页
您的设计模式
Employee
+public final static int MALE = 0
+public final static int FEMALE = 1
+public String getName()
+public void setName(String name)
+public int getSalary()
+public void setSalary(int salary)
+public int getSex()
+public void setSex(int sex)
+public final void report()
+public abstract void accept(IVisitor visitor)
CommonEmployee
+public String getJob()
+public void setJob(String job)
+public void accept(IVisitor visitor)
Manager
+public String getPerformance()
+public void setPerformance(String performance)
+public void accept(IVisitor visitor)
IVisitor
<>
+public void visit(CommonEmployee commonEmployee)
+public void visit(Manager manager)
ITotalVisitor
<>
+public void totalSalary()
IShowVisitor
<>
+public void report()
TotalVisitor ShowVisitor
Client
负责汇总表
负责展示表
调用者有较多的改动
参考程序
类图看着挺恐怖,其实也没啥复杂的,多了两个接口和两个实现类,分别负责展示表和汇总表的业务
处理,我们先看 IVisitor 接口程序:
package com.cbf4life.extend2;
/**
* @author cbf4Life cbf4life@126.com
第  230  页
您的设计模式
* I’m glad to share my knowledge with you all.
* 访问者,我要去访问人家的数据了
*/
public interface IVisitor {
// 首先定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
// 其次定义,我还可以访问部门经理
public void visit(Manager manager);
}
该接口定义其下的实现类能够访问哪些类,子接口定义了具体访问者的任务和责任,先看
ITotalVisitor 接口:
package com.cbf4life.extend2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 负责统计报表的产生
*/
public interface ITotalVisitor extends IVisitor {
// 统计所有员工工资总和
public void totalSalary();
}
就一句话,非常简单,我们再来看展示表:
package com.cbf4life.extend2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 负责展示报表的产生
*/
public interface IShowVisitor extends IVisitor {
// 展示报表
public void report();
}
第  231  页
您的设计模式
也是就一句话,我们展示表访问者的实现类:
package com.cbf4life.extend2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 展示报表,该访问者的工作就是看到什么数据展示什么数据
*/
public class ShowVisitor implements IShowVisitor {
private String info = “”;
// 打印出报表
public void report() {
System.out.println(this.info);
}
// 访问普通员工,组装信息
public void visit(CommonEmployee commonEmployee) {
this.info = this.info + this.getBasicInfo(commonEmployee)+ ” 工作:
“+commonEmployee.getJob()+”\t\n”;
}
// 访问经理,然后组装信息
public void visit(Manager manager) {
this.info = this.info + this.getBasicInfo(manager) + ” 业绩:
“+manager.getPerformance() + “\t\n”;
}
// 组装出基本信息
private String getBasicInfo(Employee employee){
String info = ” 姓名: ” + employee.getName() + “\t”;
info = info + ” 性别: ” + (employee.getSex() == Employee.FEMALE?” 女 “:” 男 “)
+ “\t”;
info = info + ” 薪水: ” + employee.getSalary() + “\t”;
return info;
}
}
下面是汇总表访问者:
第  232  页
您的设计模式
package com.cbf4life.extend2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 汇总表,该访问者起汇总作用,把容器中的数据一个一个遍历,然后汇总
*/
public class TotalVisitor implements ITotalVisitor {
// 部门经理的工资系数是 5
private final static int MANAGER_COEFFICIENT = 5;
// 员工的工资系数是 2
private final static int COMMONEMPLOYEE_COEFFICIENT = 2;
// 普通员工的工资总和
private int commonTotalSalary = 0;
// 部门经理的工资总和
private int managerTotalSalary =0;
public void totalSalary() {
System.out.println(” 本公司的月工资总额是 ” + (this.commonTotalSalary +
this.managerTotalSalary));
}
// 访问普通员工,计算工资总额
public void visit(CommonEmployee commonEmployee) {
this.commonTotalSalary = this.commonTotalSalary +
commonEmployee.getSalary()*COMMONEMPLOYEE_COEFFICIENT;
}
// 访问部门经理,计算工资总额
public void visit(Manager manager) {
this.managerTotalSalary = this.managerTotalSalary + manager.getSalary()
*MANAGER_COEFFICIENT ;
}
}
然后看 Client 类的修改:
第  233  页
您的设计模式
package com.cbf4life.extend2;
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Client {
public static void main(String[] args) {
// 展示报表访问者
IShowVisitor showVisitor = new ShowVisitor();
// 汇总报表的访问者
ITotalVisitor totalVisitor = new TotalVisitor();
for(Employee emp:mockEmployee()){
emp.accept(showVisitor); // 接受展示报表访问者
emp.accept(totalVisitor);// 接受汇总表访问者
}
// 展示报表
showVisitor.report();
// 汇总报表
totalVisitor.totalSalary();
}
}
运行结果如下:
姓名:张三 性别:男 薪水: 1800 工作:编写 Java 程序,绝对的蓝领、苦工加搬运工
姓名:李四 性别:女 薪水: 1900 工作:页面美工,审美素质太不流行了!
姓名:王五 性别:男 薪水: 18750 业绩:基本上是负值,但是我会拍马屁呀
本公司的月工资总额是 101150
大家可以再深入的想象,一堆数据从几个角度来分析,那是什么?数据挖掘(Data Mining) ,数据的
上切、下钻等等处理,大家有兴趣看可以翻看数据挖掘或者商业智能(BI)的书。
第  234  页
您的设计模式
拦截器。你如果用过 Struts2,对拦截器绝对不会陌生,我们先想一下拦截器有什么作用,拦截器的核
心作用是“围墙”作用,拦截器对被拦截的对象进行检查,符合规则的对象则开门放进去,继续执行下一
个逻辑,不符合规则的则弹回(其实这也是过滤器的作用) ;拦截器还有一个作用是修改数据,对于符合规
则数据可以进行修改,以便继续后序的逻辑。具备了这两个功能,拦截器的雏形就有了,访问者模式就可
以实现简单的拦截器角色,我们来看类图:
BaseAction
+public void accept(BaseInterceptor baseInterceptor)
+public void accept(List list)
Action1 Action2
Action3
BaseInterceptor
Interceptor1 Interceptor2
App
DynamicProxy
看着是不是和访问者模式的通用类图很类似?两个 accept 方法,其中参数为 List 类型的则实现了拦
截器栈的作用,DynamicProxy 类使用了动态代理和反射模式。拦截器实现起来也不复杂,今天就不实现了,
这个作为作业,请大家自己来实现。计划在混编模式中一起探讨。
第  235  页
您的设计模式
第 19  章 状态模式【State Pattern 】
现在城市发展很快,百万级人口的城市一堆一堆的,那其中有两个东西的发明在城市的发展中起到非
常重要的作用:一个是汽车,一个呢是…,猜猜看,是什么?是电梯!汽车让城市可以横向扩展,电梯让
城市可以纵向延伸,向空中伸展。汽车对城市的发展我们就不说了,电梯,你想想看,如果没有电梯,每
天你需要爬 10 层楼梯, 你是不是会崩溃掉?建筑师设计了一个没有电梯的建筑, 那投资家肯定不愿意投资,
那也是建筑师的耻辱呀,今天我们就用程序表现一下这个电梯是怎么运作的。
我们每天都在乘电梯,那我们来看看电梯有哪些动作(映射到 Java 中就是有多少方法):开门、关门、
运行、停止,就这四个动作,好,我们就用程序来实现一下电梯的动作,先看类图设计:
Lift
ILift
<>
+void open()
+void close()
+void run()
+void stop()
电梯的接口,定义了电梯
的四个方法
Client
非常简单的类图,定义一个接口,然后是一个实现类,然后业务类 Client 就可以调用,并运行起来,
简单也来看看我们的程序,先看接口:
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一个电梯的接口
*/
public interface ILift {
//首先电梯门开启动作
public void open();
//电梯门有开启,那当然也就有关闭了
第  236  页
您的设计模式
public void close();
//电梯要能上能下,跑起来
public void run();
//电梯还要能停下来,停不下来那就扯淡了
public void stop();
}
然后看实现类:
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 电梯的实现类
*/
public class Lift implements ILift {
//电梯门关闭
public void close() {
System.out.println(“电梯门关闭…”);
}
//电梯门开启
public void open() {
System.out.println(“电梯门开启…”);
}
//电梯开始跑起来
public void run() {
System.out.println(“电梯上下跑起来…”);
}
//电梯停止
public void stop() {
System.out.println(“电梯停止了…”);
}
}
电梯的开、关、跑、停都实现了,开看业务是怎么调用的:
第  237  页
您的设计模式
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 模拟电梯的动作
*/
public class Client {
public static void main(String[] args) {
ILift lift = new Lift();
//首先是电梯门开启,人进去
lift.open();
//然后电梯门关闭
lift.close();
//再然后,电梯跑起来,向上或者向下
lift.run();
//最后到达目的地,电梯挺下来
lift.stop();
}
}
运行的结果如下:
电梯门开启…
电梯门关闭…
电梯上下跑起来…
电梯停止了…
太简单的程序了,是个程序员都会写这个程序,这么简单的程序还拿出来 show,是不是太小看我们的
智商了?!非也,非也,我们继续往下分析,这个程序有什么问题,你想呀电梯门可以打开,但不是随时
都可以开,是有前提条件的的,你不可能电梯在运行的时候突然开门吧?!电梯也不会出现停止了但是不
开门的情况吧?!那要是有也是事故嘛,再仔细想想,电梯的这四个动作的执行都是有前置条件,具体点
说说在特定状态下才能做特定事,那我们来分析一下电梯有什么那些特定状态:
门敞状态—按了电梯上下按钮,电梯门开,这中间有 5 秒的时间(当然你也可以用身体挡住电梯门,
那就不是 5 秒了) ,那就是门敞状态;在这个状态下电梯只能做的动作是关门动作,做别的动作?那就危险
第  238  页
您的设计模式

门闭状态—电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了) 、停止(忘
记按路层号了) 、运行
运行状态—电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止;
停止状态—电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作;
我们用一张表来表示电梯状态和动作之间的关系:
开门(open) 关门(close) 运行(run) 停止(stop)
门敞状态 ○ ☆ ○ ○
门闭状态 ☆ ○ ☆ ☆
运行状态 ○ ○ ○ ☆
停止状态 ☆ ○ ☆ ○
电梯状态和动作对应表(○表示不允许, ☆表示允许动作)
看到这张表后,我们才发觉,哦~~,我们的程序做的很不严谨,好,我们来修改一下,先看类图:
ILift
<>
+public final static int OPENING_STATE = 1
+public final static int CLOSING_STATE = 2
+public final static int RUNNING_STATE = 3
+public final static int STOPPING_STATE = 4
+void open()
+void close()
+void run()
+void stop()
Lift
-private void openWithoutLogic()
-private void closeWithoutLogic()
-private void runWithoutLogic()
-private void stopWithoutLogic()
Client
4 个常量定义了电梯的状态
实现类实现了接口的 4 个方法
xxxWithoutLogic() 是实现动作的发生,不考虑业务逻辑
增加了状态的类图
在接口中定义了四个常量,分别表示电梯的四个状态:门敞状态、关闭状态、运行状态、停止状态,
然后在实现类中电梯的每一次动作发生都要对状态进行判断,判断是否运行执行,也就是动作的执行是否
符合业务逻辑,实现类中的四个私有方法是仅仅实现电梯的动作,没有任何的前置条件,因此这四个方法
是不能为外部类调用的,设置为私有方法。我们先看接口的改变:
package com.cbf4life.common2;
/**
第  239  页
您的设计模式
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一个电梯的接口
*/
public interface ILift {
//电梯的四个状态
public final static int OPENING_STATE = 1; //门敞状态
public final static int CLOSING_STATE = 2; //门闭状态
public final static int RUNNING_STATE = 3; //运行状态
public final static int STOPPING_STATE = 4; //停止状态;
//设置电梯的状态
public void setState(int state);
//首先电梯门开启动作
public void open();
//电梯门有开启,那当然也就有关闭了
public void close();
//电梯要能上能下,跑起来
public void run();
//电梯还要能停下来,停不下来那就扯淡了
public void stop();
}
增加了四个静态常量,增加了一个方法 setState,设置电梯的状态。我们再来看实现类是如何实现的:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 电梯的实现类
*/
public class Lift implements ILift {
private int state;
public void setState(int state) {
this.state = state;
}
第  240  页
您的设计模式
//电梯门关闭
public void close() {
//电梯在什么状态下才能关闭
switch(this.state){
case OPENING_STATE: //如果是则可以关门,同时修改电梯状态
this.closeWithoutLogic();
this.setState(CLOSING_STATE);
break;
case CLOSING_STATE: //如果电梯就是关门状态,则什么都不做
//do nothing;
break;
case RUNNING_STATE: //如果是正在运行,门本来就是关闭的,也说明都不做
//do nothing;
break;
case STOPPING_STATE: //如果是停止状态,本也是关闭的,什么也不做
//do nothing;
break;
}
}
//电梯门开启
public void open() {
//电梯在什么状态才能开启
switch(this.state){
case OPENING_STATE: //如果已经在门敞状态,则什么都不做
//do nothing;
break;
case CLOSING_STATE: //如是电梯时关闭状态,则可以开启
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
case RUNNING_STATE: //正在运行状态,则不能开门,什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,淡然要开门了
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
}
}
//电梯开始跑起来
第  241  页
您的设计模式
public void run() {
switch(this.state){
case OPENING_STATE: //如果已经在门敞状态,则不你能运行,什么都不做
//do nothing;
break;
case CLOSING_STATE: //如是电梯时关闭状态,则可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
break;
case RUNNING_STATE: //正在运行状态,则什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
}
}
//电梯停止
public void stop() {
switch(this.state){
case OPENING_STATE: //如果已经在门敞状态,那肯定要先停下来的,什么都不做
//do nothing;
break;
case CLOSING_STATE: //如是电梯时关闭状态,则当然可以停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case RUNNING_STATE: //正在运行状态,有运行当然那也就有停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case STOPPING_STATE: //停止状态,什么都不做
//do nothing;
break;
}
}
//纯粹的电梯关门,不考虑实际的逻辑
private void closeWithoutLogic(){
System.out.println(“电梯门关闭…”);
}
//纯粹的店门开,不考虑任何条件
第  242  页
您的设计模式
private void openWithoutLogic(){
System.out.println(“电梯门开启…”);
}
//纯粹的运行,不考虑其他条件
private void runWithoutLogic(){
System.out.println(“电梯上下跑起来…”);
}
//单纯的停止,不考虑其他条件
private void stopWithoutLogic(){
System.out.println(“电梯停止了…”);
}
}
程序有点长,但是还是很简单的,就是在每一个接口定义的方法中使用 witch…case 来进行判断,是
否运行运行指定的动作。我们来 Client 程序的变更:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 模拟电梯的动作
*/
public class Client {
public static void main(String[] args) {
ILift lift = new Lift();
//电梯的初始条件应该是停止状态
lift.setState(ILift.STOPPING_STATE);
//首先是电梯门开启,人进去
lift.open();
//然后电梯门关闭
lift.close();
//再然后,电梯跑起来,向上或者向下
lift.run();
第  243  页
您的设计模式
//最后到达目的地,电梯挺下来
lift.stop();
}
}
业务调用的方法中增加了电梯状态判断,电梯要开门不是随时都可以开的,必须满足了一定条件你才
能开门,人才能走进去,我们设置电梯的起始是停止状态,看运行结果:
电梯门开启…
电梯门关闭…
电梯上下跑起来…
电梯停止了…
我们来想一下,这段程序有什么问题,首先 Lift.java 这个文件有点长,长的原因是我们在程序中使
用了大量的 switch…case 这样的判断(if…else 也是一样) ,程序中只要你有这样的判断就避免不了加长
程序,同步的在业务比较复杂的情况下,程序体会更长,这个就不是一个很好的习惯了,较长的方法或者
类的维护性比较差,毕竟程序是给人来阅读的;其次,扩展性非常的不好,大家来想想,电梯还有两个状
态没有加, 是什么?通电状态和断电状态, 你要是在程序再增加这两个方法, 你看看 Open()、 Close()、 Run()、
Stop()这四个方法都要增加判断条件,也就是说 switch 判断体中还要增加 case 项,也就说与开闭原则相
违背了;再其次,我们来思考我们的业务,电梯在门敞开状态下就不能上下跑了吗?电梯有没有发生过只
有运行没有停止状态呢(从 40 层直接坠到 1 层嘛)?电梯故障嘛,还有电梯在检修的时候,可以在 stop
状态下不开门,这也是正常的业务需求呀,你想想看,如果加上这些判断条件,上面的程序有多少需要修
改?虽然这些都是电梯的业务逻辑,但是一个类有且仅有一个原因引起类的变化,单一职责原则,看看我
们的类,业务上的任务一个小小增加或改动都对我们的这个电梯类产生了修改,这是在项目开发上是有很
大风险的。既然我们已经发现程序上有以上问题,我们怎么来修改呢?
刚刚我们是从电梯的有哪些方法以及这些方法执行的条件去分析,现在我们换个角度来看问题,我们
来想电梯在具有这些状态的时候,能够做什么事情,也就是说在电梯处于一个具体状态时,我们来思考这
个状态是由什么动作触发而产生以及在这个状态下电梯还能做什么事情,举个例子来说,电梯在停止状态
时,我们来思考两个问题:
第一、这个停止状态时怎么来的,那当然是由于电梯执行了 stop 方法而来的;
第二、在停止状态下,电梯还能做什么动作?继续运行?开门?那当然都可以了。
我们再来分析其他三个状态,也都是一样的结果,我们只要实现电梯在一个状态下的两个任务模型就
第  244  页
您的设计模式
可以了:这个状态是如何产生的以及在这个状态下还能做什么其他动作(也就是这个状态怎么过渡到其他
状态) ,既然我们以状态为参考模型,那我们就先定义电梯的状态接口,思考过后我们来看类图:
LiftState

protected Context context

+public void setContext(Context _context)
+public abstract void open()
+public abstract void close()
+public abstract void run()
+public abstract void stop()
Context
+public LiftState getLiftState()
+public void setLiftState(LiftState liftState)
+public void open()
+public void close()
+public void run()
+public void stop()
OpenningState ClosingState RunningState Stopping
Client
每个状态下还是有四个方法,
各个状态下的实现方法是不同的
承担的是环境角色,也就是封装类
以状态作为导向的类图
在类图中,定义了一个 LiftState 抽象类,声明了一个受保护的类型 Context 变量,这个是串联我们
各个状态的封装类,封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法
则了,我的类内部情节你知道越少越好,并且还定义了四个具体的实现类,承担的是状态的产生以及状态
间的转换过渡,我们先来看 LiftState 程序:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 定义一个电梯的接口
*/
public abstract class LiftState{
//定义一个环境角色,也就是封装状态的变换引起的功能变化
protected Context context;
public void setContext(Context _context){
this.context = _context;
}
第  245  页
您的设计模式
//首先电梯门开启动作
public abstract void open();
//电梯门有开启,那当然也就有关闭了
public abstract void close();
//电梯要能上能下,跑起来
public abstract void run();
//电梯还要能停下来,停不下来那就扯淡了
public abstract void stop();
}
抽象类比较简单,我们来先看一个具体的实现,门敞状态的实现类:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 在电梯门开启的状态下能做什么事情
*/
public class OpenningState extends LiftState {
//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行
super.context.getLiftState().close();
}
//打开电梯门
@Override
public void open() {
System.out.println(“电梯门开启…”);
}
//门开着电梯就想跑,这电梯,吓死你!
@Override
public void run() {
第  246  页
您的设计模式
//do nothing;
}
//开门还不停止?
public void stop() {
//do nothing;
}
}
我来解释一下这个类的几个方法,Openning 状态是由 open()方法产生的,因此这个方法中有一个具体
的业务逻辑,我们是用 print 来代替了;在 Openning 状态下,电梯能过渡到其他什么状态呢?按照现在的
定义的是只能过渡到 Closing 状态,因此我们在 Close()中定义了状态变更,同时把 Close 这个动作也委托
了给 CloseState 类下的 Close 方法执行,这个可能不好理解,我们再看看 Context 类就可能好理解一点:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
*/
public class Context {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();
public final static ClosingState closeingState = new ClosingState();
public final static RunningState runningState = new RunningState();
public final static StoppingState stoppingState = new StoppingState();
//定一个当前电梯状态
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//把当前的环境通知到各个实现类中
this.liftState.setContext(this);
}
第  247  页
您的设计模式
public void open(){
this.liftState.open();
}
public void close(){
this.liftState.close();
}
public void run(){
this.liftState.run();
}
public void stop(){
this.liftState.stop();
}
}
结合以上三个类,我们可以这样理解,Context 是一个环境角色,它的作用是串联各个状态的过渡,在
LiftSate 抽象类中我们定义了并把这个环境角色聚合进来,并传递到了子类,也就是四个具体的实现类中
自己根据环境来决定如何进行状态的过渡。我们把其他的三个具体实现类阅读完毕,下面是关闭状态:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 电梯门关闭以后,电梯可以做哪些事情
*/
public class ClosingState extends LiftState {
//电梯门关闭,这是关闭状态要实现的动作
@Override
public void close() {
System.out.println(“电梯门关闭…”);
}
//电梯门关了再打开,逗你玩呢,那这个允许呀
@Override
public void open() {
super.context.setLiftState(Context.openningState); //置为门敞状态
super.context.getLiftState().open();
第  248  页
您的设计模式
}
//电梯门关了就跑,这是再正常不过了
@Override
public void run() {
super.context.setLiftState(Context.runningState); //设置为运行状态;
super.context.getLiftState().run();
}
//电梯门关着,我就不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState); //设置为停止状态;
super.context.getLiftState().stop();
}
}
下面是电梯的运行状态:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 电梯在运行状态下能做哪些动作
*/
public class RunningState extends LiftState {
//电梯门关闭?这是肯定了
@Override
public void close() {
//do nothing
}
//运行的时候开电梯门?你疯了!电梯不会给你开的
@Override
public void open() {
//do nothing
}
//这是在运行状态下要实现的方法
@Override
第  249  页
您的设计模式
public void run() {
System.out.println(“电梯上下跑…”);
}
//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState); //环境设置为停止状态;
super.context.getLiftState().stop();
}
}
下面是停止状态:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 在停止状态下能做什么事情
*/
public class StoppingState extends LiftState {
//停止状态关门?电梯门本来就是关着的!
@Override
public void close() {
//do nothing;
}
//停止状态,开门,那是要的!
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.getLiftState().open();
}
//停止状态再跑起来,正常的很
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}
第  250  页
您的设计模式
//停止状态是怎么发生的呢?当然是停止方法执行了
@Override
public void stop() {
System.out.println(“电梯停止了…”);
}
}
业务逻辑都已经实现了,我们来看看 Client 怎么实现:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 模拟电梯的动作
*/
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}
Client 调用类太简单了,只要定义个电梯的初始状态,然后调用相关的方法,就完成了,完全不用考
虑状态的变更,看运行结果:
电梯门开启…
电梯门关闭…
电梯上下跑起来…
电梯停止了…
我们再来回顾一下我们刚刚批判上一段的代码,首先我们说人家代码太长,这个问题我们解决了,通
第  251  页
您的设计模式
过各个子类来实现,每个子类的代码都很短,而且也取消了的 switch…case 条件的判断;其次,说人家不
符合开闭原则,那如果在我们这个例子中要增加两个状态怎么加?增加两个子类,一个是通电状态,一个
是断电状态,同时修改其他实现类的相应方法,因为状态要过渡呀,那当然要修改原有的类,只是在原有
类中的方法上增加,而不去做修改;再其次,我们说人家不符合迪米特法则,我们现在呢是各个状态是单
独的一个类,只有与这个状态的有关的因素修改了这个类才修改,符合迪米特法则,非常完美!
上面例子中多次提到状态,那我们这节讲的就是状态模式,什么是状态模式呢?当一个对象内在状态
改变时允许其改变行为,这个对象看起来像是改变了其类
当一个对象内在状态
改变时允许其改变行为,这个对象看起来像是改变了其类。说实话,这个定义的后半句我也没看懂,看过
GOF 才明白是怎么回事: Allow an object to alter its behavior when its internal state changes. The
object will appear to change its class. [GoF, p305],也就是说状态模式封装的非常好,状态的变更
引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。状态模式的通用实现类如下:
Context
+Request()
State
+Handle()
ConcreteState
+state
状态模式通用类图
状态模式中有什么优点呢?首先是避免了过多的 swith…case 或者 if..else 语句的使用,避免了程序
的复杂性;其次是很好的使用体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就
增加子类,你要修改状态,你只修改一个子类就可以了;最后一个好处就是封装性非常好,这也是状态模
式的基本要求,状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变
换。
状态模式既然有优点,那当然有缺点了,只有一个缺点,子类会太多,也就是类膨胀,你想一个事物
有七八、十来个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,这个需要大家在项
目自己衡量。其实有很大方式解决这个状态问题,比如在数据库中建立一个状态表,然后根据状态执行相
应的操作,这个也不复杂,看大家的习惯和嗜好了。
状态模式使用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说行
为是受状态约束的情况下可以使用状态模式,而且状态模式使用时对象的状态最好不要超过五个,防止你
写子类写疯掉。
第  252  页
您的设计模式
上面的例子可能比较复杂,请各位看官耐心的看,看完我想肯定有所收获。我翻遍了所有能找的到的
资料(至少也有十几本,其中有几本原文的书还是很的很不错的,我举这个电梯的例子也是从《Design
Pattern for Dummies》这本书来激发出来的) ,基本(基本哦,还是有几本讲的不错)上没有一本把这个
状态模式讲透彻的,我不敢说我就讲的透彻,大家都只讲了一个状态到另一个状态过渡,状态间的过渡是
固定的,举个简单的例子:
状态 A 状态 B 状态 C 状态 D
这个状态图是很多书上都有的,状态 A 只能变更到状态 B,状态 B 再变更到状态 C,例子举的最多的就
是 TCP 监听的例子,TCP 有三个状态:等待,连接,断开,然后这三个状态中按照顺序循环变更,按照这个
状态变更来讲解状态模式,我认为是不太合适的,为什么呢?你在项目中太少看到一个状态只能过渡到另
一个状态情形,项目中遇到的大多数情况都是一个状态可以转换为几种状态,如下图:
状态 A 状态 B 状态 C 状态 D
状态 B 可以转换为状态 C 也可以转换为状态 D,而状态 D 呢也可以转换为状态 A 或状态 B,这在项目分
析过程中有一个叫状态图可以完整的展示这种蜘蛛网结构,举个实际例子来说,一些收费网站的用户就有
很多状态,比如普通用户,普通会员,VIP 会员,白金级用户等等,这个状态的变更你不允许跳跃?!这不
可能,所以我在例子中就举了一个比较复杂的应用,基本上可以实现状态间自由切换,这才是最经常用到
的状态模式。然后我再提问个问题,状态间的自由切换,那会有很多种呀,你要一个一个的牢记一遍吗?
比如上面那个电梯的例子,我要一个正常的电梯运行逻辑,规则是开门->关门->运行->停止;还要一个紧
急状态(比如火灾)下的运行逻辑,关门->停止,紧急状态电梯当然不能用了;再要一个维修状态下的运
行逻辑, 这个状态任何情况都可以, 开着门电梯运行?可以! 门来回开关?可以! 永久停止不动?可以! 那
这怎么实现呢?需要我们把已经有的几种状态按照一定的顺序再重新组装一下,那这个是什么模式?什么
模式?大声点!建造者模式!对,建造模式+状态模式会起到非常好的封装作用。
第  253  页
您的设计模式
再往深里面扯几句,应该有部分读者做过工作流开发,如果不是土制框架的话,就应该有个状态机管
理(即使是土制框架也应该有) ,比如一个 Activity(节点)有初始化状态(Initialized State) 、挂起状
态(Suspended State) 、完成状态(Completed State)等等,流程实例也是有这么多状态,那这些状态怎
么管理呢?通过状态机(State Machine)来管理,那状态机是个什么东西呢?就是我们上面提到的 Context
类的升级变态 BOSS!
第  254  页
您的设计模式
第 20  章 原型模式【Prototype Pattern 】
计划完成日期:2009 年 7 月 12 日
第  255  页
您的设计模式
第 21  章 中介者模式【Mediator Pattern 】
计划完成日期:2009 年 7 月 19 日
第  256  页
您的设计模式
第 22  章 解释器模式【Interpreter Pattern 】
计划完成日期:2009 年 7 月 19 日
第  257  页
您的设计模式
第 23  章 亨元模式【Flyweight Pattern 】
计划完成日期:2009 年 7 月 26 日
第  258  页
您的设计模式
第 24  章 备忘录模式【Memento Pattern 】
计划完成日期:2009 年 7 月 26 日
第  259  页
您的设计模式
第 25  章 模式大 PK
计划完成时间:2009 年 8 月 31 日
第  260  页
您的设计模式
第 26  章 六大设计原则
26.1 单一职责原则【Single Responsibility Principle 】 
? 我是“牛”类,我可以担任多职吗?
单一职责原则简称是 SRP,就是三个开通字母的缩写,这个设计原则备受争议的一个原则,只要你想和
人争执、怄气或者是吵架,这个原则是屡试不爽的,如果你是老大,看到一个接口或类是这样…那样…设
计的,你就问一句“你设计的类符合 SRP 原则吗?” ,保准立马萎缩掉,而且还一脸崇拜的看着你“老大确
实英明” ,这个原则争论在什么地方呢?就是职责的定义,什么是类的职责,怎么划分类的职责。我们先讲
个例子来说明什么是单一职责原则。
只要做过项目,肯定要接触到用户、机构、角色管理这个模块,基本使用都是 RBAC 这个模型,确实是
很好的一个解决办法,我们今天要讲的是用户管理,管理用户的信息,增加机构(一个人属于多个机构) ,
增加角色等,用户有这么的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类嘛,我们
先来看类图:
IUserInfo
<>
+public void setUserID(String userID)
+public String getUserID()
+public void setPassword(String password)
+public String getPassword()
+public void setUserName(String userName)
+public String getUserName()
+public boolean changePassword(String oldPassword)
+public boolean deleteUser()
+public void mapUser()
+public boolean addOrg(int orgID)
+public boolean addRole(int roleID)
UserInfo
太 easy 的类图了,我相信即使一个初级的程序员也可以看出这个接口设计的有问题,用户的属性
(Properties)和用户的行为(Behavior)没有分开,这是一个严重的错误!非常正确,确实是这个接口
设计的一团糟,应该把用户的信息抽取成一个业务对象(Bussiness Object,简称 BO),把行为抽取成另外一
第  261  页
您的设计模式
到另外一个接口中,我们把这个类图重新画一下:
IUserInfo
<>
UserInfo
IUserBO
<>
+public void setUserID(String userID)
+public String getUserID()
+public void setPassword(String password)
+public String getPassword()
+public void setUserName(String userName)
+public String getUserName()
IUserBiz
<>
+public boolean changePassword(IUserBO userBO, String oldPassword)
+public boolean deleteUser(IUserBO userBO)
+public void mapUser(IUserBO userBO)
+public boolean addOrg(IUserBO userBO, int orgID)
+public boolean addRole(IUserBO userBO, int roleID)
负责用户的属性 负责用户的行为
重新拆封成两个接口,IUserBO 负责用户的属性,简单的说就是 IUserBO 的职责是收集和反馈用户的属
性信息;IUserBiz 负责用户的行为,完成用户的信息的维护和变更。各位可能要问了,这个和我实际的工
作中用到的 User 类还是有差别的呀,别着急,我们先来想一想分拆成两个接口怎么使用,想清楚了,我们
看是面向的接口编程,所有呀你产生了这个 UserInfo 对象之后,你当然可以把它当 IUserBO 接口使用,当
然也可以当 IUserBiz 接口使用,这要看你在怎么地方使用了,你要获得用户想信息,你就当时 IUserBO 的
实现类;你要是想维护用户的信息,就当是 IUserBiz 的实现类就成了,类似于如下代码:
…….
IUserBiz userInfo = new UserInfo();
//我要赋值了,我就认为它是一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword(“abc”);
//我要执行动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
…….
第  262  页
您的设计模式
确实可以如此,问题也解决掉了,但是我们来回想一下我们刚刚的动作,为什么要把一个接口拆分成
两个?其实在实际的使用中, 我们更倾向于使用两个不同的类或接口,一个就是 IUserBO, 一个是IUserBiz,
类图应该如如下图:
IUserBO
<>
IUserBiz
<>
UserBO
UserBiz
以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?
单一职责原则:应该有且仅有一个原因引起类的变更
? 绝杀技,打破你的传统思维
我解释到这里估计你现在很不屑了, “切!这么简单的东西还要讲?!弱智!”,好,我们来讲点复杂的。
SRP 的原话解释是:There should never be more than one reason for a class to change。这句话初中
生都能看懂,不多说,但是看懂是一回事,实施就是另外一回事了,上面我说的例子很好理解,大家已经
都是这么做了,那我再举个例子来看看是否好理解。举个电话的例子,电话通话的时候有四过程发生:拨
号、通话、回应、挂机,那我们写一个接口应该如下这个类图:
IPhone
+public void dial(String phoneNumber)
+public void chat(Object o)
+public void answer(Object o)
+public void huangup()
我不是有意要冒犯 IPhone 的,同名纯属巧合,我们来看一个接口程序:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
第  263  页
您的设计模式
* 电话的接口
*/
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//回应,只有自己说话而没有回应,那算啥?!
public void answer(Object o);
//通话完毕,挂电话
public void huangup();
}
实现类我就不再写了,大家看看这个接口有没有问题?我相信大部分的读者都会说这个没有问题呀,
以前我就是这么做的呀,XXX 这本书上也是这么写的呀,还有什么什么的源码也是这么写的,是的,这个接
口接近于完美,看清楚了是“接近于” 。单一职责要求一个接口或类只有一个原因引起变化,也就是一个接
口或类只有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?是只有一个原因引起变化
吗?好像不是!
IPhone 这个接口可不是只有一个职责,它是由两个职责:一个是协议管理,一个是数据传送,diag()
和 huangup()两个方法实现的是协议管理,拨号接通和关闭;chat()和 answer()是数据的传送,把我们说
的话转换成模拟信号或者是数字信号传递到对方,然后再把对方传递过来的信号还原成我们听的懂人话。
我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想
想看,电话不仅仅可以通电话,还可以上网,Modem 拨号上网呀)的变化会引起这个接口或者实现类的变化
吗?会的!那就很简单了,这里有两个原因都引起了类的变化,而且这两个职责会相互影响吗?电话通话,
我只要能接通就成,甭管是 2G 还是 3G 的协议;电话连接后还关心传递的是什么数据吗?不关心,你要是
乐意使用 56K 的小猫传递一个高清的片子,那也没有问题(顶多有人说你 13 了) ,也就说我们现在提供的
这个 IPhone 接口 包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆开成两个接口:
第  264  页
您的设计模式
IConnectionManager
<>
+public void dial(String phoneNumber)
+public void huangup()
IDataTransfer
<>
+public DataTransfer(IConnectionManager connectionManager)
+public void chat(Object o)
+public void answer(Object o)
Phone
ConnectionManager DataTransfer
这个类图看这有点复杂了,完全满足了类和接口的单一职责要求,非常符合标准,但是我相信你在设
计的时候肯定不会采用这种方式,一个手机类要把把两个 ConnectionManager 和 DataTransfer 组合在一块
才能使用,组合是一种强耦合关系,你和我都是有共同的生命期,这样的强耦合关系还不如使用接口实现
的方式呢,而且还增加了类的复杂性,多了两个类呀,好,我们修改一下类图:
ConnectionManager
<>
+public void dial(String phoneNumber)
+public void huangup()
DataTransfer
<>
+public void chat(Object o)
+public void answer(Object o)
Phone
这样的设计才是完美的,一个手机实现了两个接口,把两个职责融合一个类中,你会觉得这个 Phone
有两个原因引起变化了呀,是的是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是
实现类;而且如果真要实现类的单一职责的话,这个就必须使用了上面组合的方式了,那这个会引起类间
耦合过重的问题(我们等会再说明这个问题,继续往下看) 。那使用了单一职责原则有什么好处呢?
类的复杂性降低,实现什么职责都有清晰明确的定义;
可读性提高,复杂性降低,那当然可读性提高了;
可维护性提高,那当然了,可读性提高,那当然更容易维护了;
第  265  页
您的设计模式
变更引起的风险降低,变更是必不可少的,接口的单一职责做的好的话,一个接口修改只对相应的实
现类有影响,与其他的接口无影响,这个是对项目有非常大的帮助。
讲过这个例子后是不是有点凡反思了,我以前的设计是不是有点的问题了?是的,单一职责原则最难
划分的就是职责,一个职责一个接口,但是问题是“职责”是一个没有量化的标准,一个类到底要负责那
些职责?这些职责怎么细化?细化后是否都要有一个接口或类?这个都是需要从实际的项目区考虑的,从
功能上来说,定义一个 IPhone 接口也没有错,实现了电话的功能呀,而且设计还很简单,就一个接口一个
实现类,真正的项目我想大家一般都是会这么设计的,从设计原则上来看就有问题了,有两个可以变化的
原因放到了一个接口中了,这就为以后的变化带来了风险,我从 2G 通讯协议修改到 3G 通讯,你看看你提
供出的接口 IPhone 是不是要修改了?接口修改对其他的 Invoker 是不是有很大影响?!
? 我单纯,所有我快乐
对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了,生搬硬套单一
职责原则会引起类的剧增,给维护带来非常多的麻烦;而且过分的细分类的职责也为人为的制造系统的负
责性,本来一个类可以实现的行为非要拆成两个,然后使用聚合或组合的方式再耦合在一起,这个是人为
制造了系统的复杂性,所以原则是死的,人是活的,这句话是非常好的。
单一职责原则很难在项目中得到体现,非常难,为什么?考虑项目环境,考虑工作量,考虑人员的技
术水平,考虑硬件的资源情况等等,最终融合的结果是经常违背这一单一原则。而且我们中华文明就有很
多属于混合型的产物,比如筷子,我们使用筷子可以当做刀来使用,分割食物;还可以当叉使用,把食物
从盘子中移动到口中;而在西方的文化中,刀就是刀,叉就是叉,你去吃西餐的时候这两样肯定都是有的,
分工很明晰,刀就是切割食物,叉就是固定食物或者移动食物,这个中国的文化有非常大的关系,中国文
化就是一个综合性的文化,要求一个人或是一个事物能够满足多个用途,一个人既要求你会技术还要会管
理,还要会财务,要计算成本呀,这个是从小兵就开始要求了,哎,比较悲哀!我相信如果电脑是中国发
明的话,肯定是这个样子:CPU、内存、主板、电源全部融合在一起,什么逻辑运算,数据处理,图像显示
全部放一块,呵呵,有点愤青的成分在里面了。但是我相信随着技术的深入,单一职责原则必然会深入到
项目的设计中去,而且这个原则是那么的简单,简单的我都不知道该怎么更加详细的去描述,单从字面上
大家都应该知道是什么意思,单一职责嘛!
单一职责使用于接口、类,同时也使用方法,什么意思呢?一个方法尽可能做一件事情,比如一个方
法修改用户密码,别把这个方法放到“修改用户信息”方法中,这个方法的颗粒度很粗,比如这个一个方
法:
第  266  页
您的设计模式
IUserManager
<>
+public void changeUser(IUserBO userBO, int type, String…changeOptions)
在 IUserManager 中定义了一个方法叫 changeUser,根据传递的 type 不同,把可变长度参数
changeOptions 修改到 userBo 这个对象上,并调用持久层的方法保存到数据库中。在我的项目组中如果有
人写了这样一个方法,我不管他写了多上程序化了多少工夫,一律重写!原因是:方法职责不清晰,不单
一,一般方法设计成这样的:
IUserManager
<>
+public void changeUserName(String newUserName)
+public void changeHomeAddress(String newHomeAddress)
+public void changeOfficeTel(String telNumber)
你要修改用户名称,就调用 changeUserName 方法,你要修改家庭地址就调用 changeHomeAddress,你
要修改单位单户就调用 changeOfficeTel 方法,每个方法的职责就非常清晰,这也是一个良好的设计习惯。
所以,不管是对接口、类、方法使用了单一规则原则,那么快乐的就不仅仅是你了,还有你项目的成
员,你的板,减少了因为变更引起的工作量呀,加官进爵等着你幺!
你看到这里,就会问我,你写是类的设计原则吗?你通篇都在说接口的单一职责,类的单一职责你都
违背了呀,呵呵,这个还真是的,我的本意是想把这个原则讲清楚,类的单一职责嘛,这个很简单,但当
我回头写的时候,发觉才不是这么回事,翻看了以前一些设计和代码,基本上拿的出手的类设计都是和单
一职责向违背的,静下心来回忆,发觉每一个类这样设计都是有原因的。这几天我查阅了 wikipedia、
oodesign 等几个网站,专家和我也有类似的经验,基本上类的单一职责都用了类似的一句话来说“This is
sometimes hard to see” ,这句话翻译过来就是“这个有时候很难说” ,是的,类的单一职责确实受非常多
的因素制约,纯理论的来讲,这个原则是非常优秀的,但是现实有现实难处,你必须去考虑项目工期、成
本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等等原因,比如 04 年就
做过一个项目,做加密处理的,甲方就甩过来一句话,你什么都不用管,就调用这个 API 就可以了,不用
考虑什么传输协议、异常处理、安全连接等等,所以我们就直接使用了 JNI 与加密厂商提供的 API 通信,
什么单一职责原则,根本就不用考虑,因为人家不公布通信接口、异常处理。
对于单一职责原则,我的建议是接口一定要做到单一职责,类设计尽量只有一个原因引起变化。
第  267  页
您的设计模式
26.2  里氏替换原则【Liskov Substitution Principle 】 
里氏替换法则是非常简单的,而且你在系统设计的时候肯定已经用到了,而且现在已经延伸到了 Web
Service 的开发领域。里氏替换法则有两种定义:
第一个定义,最正宗的定义:If for each object o1 of type S there is an object o2 of type T
such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T.
如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的
对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
第二个定义,functions that use pointers or references to base classes must be able to use
objects of derived classes without knowing it.
所有引用基类的地方必须能透明地使用其子类的对象。
第二个定义是最清晰明确的,通俗点讲只要父类能出现的地方我子类就可以出现,而且调用子类还不
产生任何的错误或异常,调用者可能根本就不需要知道是父类还是子类。但是反过来就不成了,有子类出
现的地方,父类未必就能适应,里氏替换法则包含了四层意思:
子类必须完全的实现父类的方法。我们在做系统设计时,经常会定义一个接口或者抽象类,然后编写
实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换法则。我们举个例子还说明这个法
则,大家都打过 CS 吧,非常经典的 FPS 类游戏,我们描述一下里面用到的枪,先看类图:
第  268  页
您的设计模式
AbstractGun
+public void shoot()
Handgun Rifle MachineGun
Soldier
+public void killEnemy(AbstractGun gun)
Client
步枪
手枪
机枪
枪的主要职责就是射击,怎么射击就是在各个具体的子类中定义了,手枪是单发射程比较近,步枪威
力大射程远,机枪用于扫射,然后在士兵类中定义了一个方法 killEnemy 杀敌人,使用枪来杀,具体使用
什么枪来杀敌人,调用的时候才知道,我先看 AbstractGun 类的程序:
public abstract class AbstractGun {
//枪用来干什么的?射击杀戮!
public abstract void shoot();
}
以下是三个具体的枪械的实现类:
public class Handgun extends AbstractGun {
//手枪的特点是携带方便,射程短
@Override
public void shoot() {
System.out.println(“手枪射击…”);
}
}
public class Rifle extends AbstractGun{
第  269  页
您的设计模式
//步枪的特点是射程远,威力大
public void shoot(){
System.out.println(“步枪射击…”);
}
}
public class MachineGun extends AbstractGun{
public void shoot(){
System.out.println(“机枪扫射…”);
}
}
再来看士兵类的源码:
public class Soldier {
public void killEnemy(AbstractGun gun){
System.out.println(“士兵开始杀人…”);
gun.shoot();
}
}
注意看这里黄色标记的那部分,我们要求传入进来的是一个抽象的枪,具体是手枪还是步枪需要在调
用的时候传入,我们来看 Client 类:
public class Client {
public static void main(String[] args) {
//产生三毛这个士兵
Soldier sanMao = new Soldier();
sanMao.killEnemy(new Rifle());
}
}
运行结果:
士兵开始杀人…
第  270  页
您的设计模式
步枪射击…
在这个程序中,我们给三毛这个士兵一把步枪,然后就开始杀敌了,如果三毛要使用机枪当然也可以,
直接把 sanMao.killEnemy(new Rifle()) 修改为 sanMao.killEnemy(new MachineGun()) 就可以
了,Soldier 根本就不用知道是哪个子类。我们在类中调用其他类是务必要使用父类或接口,如果不能使用
父类或接口,则说明类的设计已经违背了 LSP 原则。
在类中调用其他类是务必要使用父类或接口,如果不能使用
父类或接口,则说明类的设计已经违背了 LSP 原则。
我们再来想想,如果我们有一个玩具手枪,该怎么去定义呢?我们先在类图上增加一个类:
AbstractGun
+public void shoot()
Handgun MachineGun Rifle
Soldier
+public void killEnemy(AbstractGun gun)
Client
ToyGun
增加了一个 ToyGun 这个类,继承于 AbstractGun 抽象类。首先我们想,玩具枪是不能用来射击的,杀
不死人的, 当然你要把玩具枪往让头上砸也能砸死人, 这个不算是在 shoot 方法中的功能。 我们来看 ToyGun
类:
public class ToyGun extends AbstractGun {
//玩具枪式不能射击的,但是编译器又要求实现这个方法,怎么办?虚假一个呗!
@Override
public void shoot() {
//玩具枪不能射击,这个方法就不能实现了
}
}
然后我们来看这个场景:
public class Client {
第  271  页
您的设计模式
public static void main(String[] args) {
//产生三毛这个士兵
Soldier sanMao = new Soldier();
sanMao.killEnemy(new ToyGun());
}
}
士兵使用玩具枪来杀人了,看运行结果:
士兵开始杀人…
坏了,士兵拿着玩具枪来杀人,射击不出子弹呀!如果在 CS 游戏中有这种事情发生,那你就等着被人
爆头吧,然后看着自己凄凉的倒地。在这种情况下,我们已经发现业务调用类已经出现了问题,这个是业
务已经不能运行了,那怎么办?有两种解决办法:
Number one:在 Soldier 中增加 instanceof 的判断,如果是玩具枪,就不用来杀敌人。这个方法可以
解决问题,但是你要知道在项目中,特别是产品,增加一个类,我要让所有与这个父类有关系的类都需要
修改,你觉的可行吗?你要是在产品中出现这个问题,因为修正了这样一个 BUG,就要求所有与这个父类有
关系的类都增加一个判断?客户非跳起来跟你干!你还想要你的客户忠诚你吗?这个方案否定。
Number two:ToyGun 脱离继承,建立独立的父类,为了做到代码可以服用,可以与 AbastractGun 建立
关联委托关系,如下图:
AbstractGun
+public void shoot()
Handgun MachineGun Rifle
Soldier
+public void killEnemy(AbstractGun gun)
Client
ToyGun
AbstractToy
比如可以在 AbstractToy 中定义声音、形状都都委托给 AbstractGun,仿真枪嘛当然要让形状、声音和
真实的枪都一样了,然后两个父类下的子类各自发展,互不影响。
第  272  页
您的设计模式
在 Java 的基础知识中,每位老师都会讲继承,Java 的三大特征嘛,继承、封装、多态,继承就是告诉
你拥有父类的方法和属性,然后你就可以重写父类的方法。按照类的继承原则,我们上面的玩具枪继承
AbstractGun 也是没有问题的呀,毕竟也是枪嘛,但是到我们的具体项目中就要考虑这个问题了:子类是否
完整的实现了父类的业务,否则就会出现像上面的拿枪杀敌人时却发现是把玩具枪的笑话。
子类可以有自己的个性。子类当然可以有自己的行为和外观了,也就是方法和属性,那这里为什么要
再提呢?是因为里氏替换法则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任。
还是以刚才的那个枪械的例子为来说明,步枪有几个比较响亮的型号比如 AK47、G3 狙击步枪等,我们来看
类图:
Rifle
AK47 G3
+public void zoomOut()
Snipper
+public void killEnemy(G3 g3)
很简单,G3 继承了 Rifle 类,狙击手(Snipper)则直接使用 G3 狙击步枪,我们来看一下程序:
public class G3 extends Rifle {
//狙击枪都是携带一个精准的望远镜
public void zoomOut(){
System.out.println(“通过望远镜观看敌人…”);
}
public void shoot(){
System.out.println(“G3射击…”);
}
}
然后我们再声明一个狙击手类:
public class Snipper {
第  273  页
您的设计模式
public void killEnemy(G3 g3){
//首先看看敌人的情况,别杀死敌人,自己也被人干掉
g3.zoomOut();
//开始射击
g3.shoot();
}
}
狙击手,为什么叫 Snipper?snipe 翻译过来就是鹬,就是鹬蚌相争,渔翁得利中的那个动物,英国贵
族到印度打猎, 发现这个鹬很聪明, 人一靠近就飞走了, 没办法就开始伪装、 远程精准射击, 于是乎 snipper
就诞生了。
我们来看一下业务业务场景是怎么调用的:
public class Client {
public static void main(String[] args) {
//产生三毛这个狙击手
Snipper sanMao = new Snipper();
sanMao.killEnemy(new G3());
}
}
运行结果如下:
通过望远镜观看敌人…
G3射击…
在这里我们直接调用了子类,一个狙击手是很依赖枪支的,别说换一个型号的枪了,就是换一个同型
号的枪也会影响射击的,所以这里就直接传递进来了子类。那这个时候,我们能不能直接使用父类传递进
来呢?修改一下 Client 类:
public class Client {
public static void main(String[] args) {
//产生三毛这个狙击手
Snipper sanMao = new Snipper();
Rifle rifle = new Rifle();
第  274  页
您的设计模式
sanMao.killEnemy((G3)rifle);
}
}
显示是不行的,会在运行期报 java.lang.ClassCastException 异常,这也是大家经常说的向下转
型(downcast)是不安全的,从里氏替换法则来看,就是有子类出现的地方父类未必就可以出现。
覆盖或实现父类的方法时输入参数可以被放大。方法中的输入参数叫做前置条件,这是什么意思呢?
大家做过 Web Service 开发就应该知道有一个“契约优先”的原则,也就是先定义出 WSDL 接口,制定好双
方的开发协议,然后再各自实现。里氏替换法则也要求制定了一个契约,就是父类或接口,这种设计方法
也叫做 Design by Contract,契约优先设计,是和里氏替换法则融合在一起的。契约制定了,但是契约有
前置条件和后置条件,前置条件就是你要让我执行,就必须满足我的条件;后置条件就是我执行完了,必
须符合规定的契约。这个比较难理解,我们来看一个例子,我们先定义个 Father 类:
public class Father {
public Collection doSomething(HashMap map){
System.out.println(“父类被执行…”);
return map.values();
}
}
这个类非常简单,就是把 HashMap 转换为 Collection 集合类型,然后我们来看子类:
public class Son extends Father {
//放大输入参数类型
public Collection doSomething(Map map){
System.out.println(“子类被执行…”);
return map.values();
}
}
大家注意看黄色明显标记部分,和父类同样的一个方法名称,但是又不是重写(Override)父类的方
法,你加个@Override 试试看,报错的,为什么呢?是输入参数类型不同,编译器就不认为是重写父类的方
法了,那这是什么?是重载(Overload) !不用大惊小怪的,不在一个类就不能是重载了?继承是什么意思,
第  275  页
您的设计模式
子类拥有父类的所有属性和方法,那方法名重复输入参数类型又不相同当然是重载了。我们再来看业务调
用类:
public class Client {
public static void invoker(){
//父类存在的地方,子类就应该能够存在
Father f = new Father();
HashMap map = new HashMap();
f.doSomething(map);
}
public static void main(String[] args) {
invoker();
}
}
运行结果如下:
父类被执行…
里氏替换法则说是父类出现的地方子类就能出现,我们把上面的黄色部分修改为子类,程序如下:
public class Client {
public static void invoker(){
//父类存在的地方,子类就应该能够存在
Son f =new Son();
HashMap map = new HashMap();
f.doSomething(map);
}
public static void main(String[] args) {
invoker();
}
}
运行结果还是一样,看明白是怎么回事了吗?父类方法的输入参数是 HashMap 类型,子类的输入参数
是 Map 类型,也就是说子类的输入参数类型的范围扩大了,子类代替父类传递到调用类用,子类的方法永
第  276  页
您的设计模式
远都不回被执行,这是正确的,如果你想让子类的方法运行,你就必须重写父类的方法。大家可以这样想
想看,在一个 Invoker类中关联了一个父类,调用了一个父类的方法,子类可以重写这个方法,也可以重
载这个方法,前提是要扩大这个前置条件,就是输入参数的类型大于父类的类型覆盖范围。可能比较理难
理解, 那我们再反过来想一下, 如果 Father 类的输入参数类型大于子类的输入参数类型, 会出现什么问题?
就会出现父类存在的地方,子类就未必可以存在,因为一旦把子类作为参数传入进去,调用者就很可能进
入子类的方法范畴。我们把上面的例子修改一下,先看父类:
public class Father {
public Collection doSomething(Map map){
System.out.println(“Map 转Collection被执行”);
return map.values();
}
}
把父类的前置条件修改为 Map 类型,我们再修改一下子类方法的输入参数,相对父类缩小输入参数的
类型范围,也就是缩小前置条件:
public class Son extends Father {
//缩小输入参数范围
public Collection doSomething(HashMap map){
System.out.println(“HashMap转Collection被执行…”);
return map.values();
}
}
再来看业务场景类:
public class Client {
public static void invoker(){
//有父类的地方就有子类
Father f= new Father();
HashMap map = new HashMap();
f.doSomething(map);
}
第  277  页
您的设计模式
public static void main(String[] args) {
invoker();
}
}
运行结果如下:
父类被执行…
那我们再把里氏替换法则引入进来会有什么问题?有父类的地方子类就可以使用,好,我们把这个
Client 类修改一下,程序如下:
public class Client {
public static void invoker(){
//有父类的地方就有子类
Son f =new Son();
HashMap map = new HashMap();
f.doSomething(map);
}
public static void main(String[] args) {
invoker();
}
}
运行结果如下:
子类被执行…
完蛋了吧?!子类在没有重写父类的方法的前提下,子类方法被执行了,这个绝对会引起以后的业务
逻辑混乱,因为在项目的应用中父类一般都是抽象类,子类是实现类,你传递一个这样的实现类就会引起
一堆意想不到的业务逻辑混乱,所以子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或
者更宽松。
子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或
者更宽松。
覆盖或实现父类的方法是输出结果可以被缩小。这个是什么意思呢,父类的一个方法返回值是一个类
第  278  页
您的设计模式
型 T,子类相同方法(重载或重写)返回值为 S,那么里氏替换法则就要求 S 必须小于等于 T,也就是说要么
S 和T 是同一个类型,要么 S 是 T 的子类,为什么呢?分两种情况,如果是重写,方法的输入参数父类子类
是相同的, 两个方法的范围值 S 小于等于 T, 这个是重写的要求, 这个才是重中之重, 子类重写父类的方法,
天经地义;如果是重载,则要求方法的输入参数不相同,在里氏替换法则要求下就是子类的输入参数大于
等于父类的输入参数,那就是说你写的这个方法是不会被调用到的,参考上面讲的前置条件。
里氏替换法则诞生的目的就是加强程序的健壮性,同时版本升级也可以做到非常好的兼容性,增加子
类,原有的子类还可以继续运行。在我们项目实施中就是每个子类对应了不同的业务含义,使用父类作为
参数,传递不同的子类完成不同的业务逻辑,非常完美!
第  279  页
您的设计模式
26.3 依赖倒置原则【Dependence Inversion Principle 】  
第  280  页
您的设计模式
?
26.4 接口隔离原则【Interface Segregation Principle 】
第  281  页
您的设计模式
?
26.5 迪米特法则【Low Of Demeter 】  
迪米特法则的简写为 LoD,看清楚中间的那个 o 是小写。迪米特法则也叫做做最少知识原则(Least
Knowledge Principle,简称 LKP)说的都是一会事,一个对象应该对其他对象有最少的了解,通俗的讲一
个类对自己需要耦合或者调用的类应该知道的最少,你类内部是怎么复杂、怎么的纠缠不清都和我没关系,
那是你的类内部的事情,我就知道你提供的这么多 public 方法,我就调用这个;迪米特法则包含以下四层
意思:
只和朋友交流。迪米特还有一个英文解释叫做“Only talk to your immedate friends”,只和直接
的朋友通信,什么叫做直接的朋友呢?每个对象都必然会和其他对象有耦合关系,两个对象之间的耦合就
成为朋友关系,这种关系有很多比如组合、聚合、依赖等等。我们来说个例子说明怎么做到只和朋友交流。
说是有这么一个故事,老师想让体育委员确认一下全班女生来齐没有,就对他说: “你去把全班女生清
一下。 ”体育委员没听清楚,或者是当时脑子正在回忆什么东西,就问道:“亲哪个?”老师¥#……¥%。
我们来看这个笑话怎么用程序来实现,先看类图:
Teacher
+public void commond(GroupLeader groupLeader)
GroupLeader
+public void countGirls(List listGirls)
Girl
Client
Teacher.java 的源程序如下:
package com.cbf4life.common;
import java.util.ArrayList;
import java.util.List;
/**
第  282  页
您的设计模式
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 老师类
*/
public class Teacher {
//老师对学生发布命令,清一下女生
public void commond(GroupLeader groupLeader){
List listGirls = new ArrayList();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
//告诉体育委员开始执行清查任务
groupLeader.countGirls(listGirls);
}
}
老师就有一个方法, 发布命令给体育委员, 去清查一下女生的数量。 下面是体育委员 GroupLeader.java
的源程序:
package com.cbf4life.common;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 体育委员,这个太难翻译了都是中国的特色词汇
*/
public class GroupLeader {
//有清查女生的工作
public void countGirls(List listGirls){
System.out.println(“女生数量是:”+listGirls.size());
}
}
下面是 Girl.java,就声明一个类,没有任何的代码:
第  283  页
您的设计模式
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 女生
*/
public class Girl {
}
我们来看这个业务调用类 Client:
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 我们使用Client来描绘一下这个场景
*/
public class Client {
public static void main(String[] args) {
Teacher teacher= new Teacher();
//老师发布命令
teacher.commond(new GroupLeader());
}
}
运行的结果如下:
女生数量是:20
我们回过头来看这个程序有什么问题,首先来看 Teacher 有几个朋友,就一个 GroupLeader 类,这个
就是朋友类,朋友类是怎么定义的呢? 出现在成员变量、方法的输入输出参数中的类被称为成员朋友类,
迪米特法则说是一个类只和朋友类交流, 但是 commond 方法中我们与 Girl 类有了交流,声明了一个
List动态数组,也就是与一个陌生的类 Girl 有了交流,这个不好,那我们再来修改一下,类图还
第  284  页
您的设计模式
是不变,先修改一下 GroupLeader 类,看源码:
package com.cbf4life.common2;
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 体育委员,这个太难翻译了都是中国的特色词汇
*/
public class GroupLeader {
//有清查女生的工作
public void countGirls(){
List listGirls = new ArrayList();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
System.out.println(“女生数量是:”+listGirls.size());
}
}
下面是 Teacher.java 程序:
package com.cbf4life.common2;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 老师类
*/
public class Teacher {
//老师对学生发布命令,清一下女生
public void commond(GroupLeader groupLeader){
//告诉体育委员开始执行清查任务
groupLeader.countGirls();
}
第  285  页
您的设计模式
}
程序做了一个简单的修改,就是把 Teacher 中的对 List初始化(这个是有业务意义的,产生出
全班的所有人员)移动到了 GroupLeader 的 countGrils 方法中,避开了 Teacher 类对陌生类 Girl 的访问,
减少系统间的耦合。 记住了, 一个类只和朋友交流, 不与陌生类交流, 不要出现 getA().getB().getC().getD()
这种情况(在一种极端的情况下是允许出现这种访问:每一个点号后面的返回类型都相同) ,那当然还要和
JDK API 提供的类交流,否则你想脱离编译器存在呀!
朋友间也是有距离的。人和人之间是有距离的,太远就不是朋友了,太近就浑身不自在,这和类间关
系也是一样,即使朋友类也不能无话不说,无所不知。大家在项目中应该都碰到过这样的需求:调用一个
类,然后必须是先执行第一个方法,然后是第二个方法,根据返回结果再来看是否可以调用第三个方法,
或者第四个方法等等,我们用类图表示一下:
Wizard
+public int first()
+public int second()
+public int third()
InstallSoftware
+public void installWizard(Wizard wizard)
Client
很简单的类图,实现软件安装过程的第一步做什么、第二步做什么、第三步做什么这样一个过程,我
们来看三个类的源代码,先看 Wizard 的源代码:
package com.cbf4life.common3;
import java.util.Random;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 按照步骤执行的业务逻辑类
*/
public class Wizard {
private Random rand = new Random(System.currentTimeMillis());
//第一步
第  286  页
您的设计模式
public int first(){
System.out.println(“执行第一个方法…”);
return rand.nextInt(100);
}
//第二步
public int second(){
System.out.println(“执行第二个方法…”);
return rand.nextInt(100);
}
//第三个方法
public int third(){
System.out.println(“执行第三个方法…”);
return rand.nextInt(100);
}
}
分别定义了三个步骤方法,每个步骤中都有相关的业务逻辑完成指定的任务,我们使用一个随机函数
来代替业务执行的返回值。再来看软件安装过程 InstallSoftware 源码:
package com.cbf4life.common3;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 业务组装类,负责调用各个步骤
*/
public class InstallSoftware {
public void installWizard(Wizard wizard){
int first = wizard.first();
//根据first返回的结果,看是否需要执行second
if(first>50){
int second = wizard.second();
if(second>50){
int third = wizard.third();
if(third >50){
wizard.first();
}
}
第  287  页
您的设计模式
}
}
}
其中 installWizard 就是一个向导式的安装步骤,我们看场景是怎么调用的:
package com.cbf4life.common3;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 业务场景
*/
public class Client {
public static void main(String[] args) {
InstallSoftware invoker = new InstallSoftware();
invoker.installWizard(new Wizard());
}
}
这个程序很简单,运行结果和随机数有关,我就不粘贴上来了。我们想想这个程序有什么问题吗?
Wizard 类把太多的方法暴露给 InstallSoftware 类了,这样耦合关系就非常紧了,我想修改一个方法的返
回值,本来是 int 的,现在修改为 boolean,你看就需要修改其他的类,这样的耦合是极度不合适的,迪米
特法则就要求类“小气”一点,尽量不要对外公布太多的 public 方法和非静态的 public 变量
迪米
特法则就要求类“小气”一点,尽量不要对外公布太多的 public 方法和非静态的 public 变量,尽量内敛,
多使用 private,package-private、protected 等访问权限
尽量内敛,
多使用 private,package-private、protected 等访问权限。我们来修改一下类图:
Wizard
+public int first()
+public int second()
+public int third()
+public void installWizard(()
InstallSoftware
+public void installWizard(Wizard wizard)
Client
我们再来看一下程序的变更,先看 Wizard 程序:
第  288  页
您的设计模式
package com.cbf4life.common4;
import java.util.Random;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 按照步骤执行的业务逻辑类
*/
public class Wizard {
private Random rand = new Random(System.currentTimeMillis());
//第一步
private int first(){
System.out.println(“执行第一个方法…”);
return rand.nextInt(100);
}
//第二步
private int second(){
System.out.println(“执行第二个方法…”);
return rand.nextInt(100);
}
//第三个方法
private int third(){
System.out.println(“执行第三个方法…”);
return rand.nextInt(100);
}
//软件安装过程
public void installWizard(){
int first = this.first();
//根据first返回的结果,看是否需要执行second
if(first>50){
int second = this.second();
if(second>50){
int third = this.third();
if(third >50){
this.first();
}
}
}
}
第  289  页
您的设计模式
}
三个步骤的访问权限修改为 private,同时把 installeWizad 移动的 Wizard 方法中,这样 Wizard 类就
对外只公布了一个 public 方法,类的高内聚特定显示出来了。我们再来看 InstallSoftware 源码:
package com.cbf4life.common4;
/**
* @author cbf4Life cbf4life@126.com
* I’m glad to share my knowledge with you all.
* 业务组装类,负责调用各个步骤
*/
public class InstallSoftware {
public void installWizard(Wizard wizard){
//不废话,直接调用
wizard.installWizard();
}
}
Client 类没有任何改变,就不在拷贝了,这样我们的程序就做到了弱耦合,一个类公布越多的 public
属性或方法,修改的涉及面也就越大,也就是变更引起的风险就越大。因此为了保持朋友类间的距离,你
需要做的是:减少 public 方法,多使用 private、package-private(这个就是包类型,在类、方法、变量
前不加访问权限,则默认为包类型)protected 等访问权限,减少非 static 的 public 属性,如果成员变量
或方法能加上 final 关键字就加上,不要让外部去改变它。
是自己的就是自己的。在项目中有一些方法,放在本类中也可以,放在其他类中也没有错误,那怎么
去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,即不增加类间关系,也对本类不产生负
面影响,就放置在本类中
如果一个方法放在本类中,即不增加类间关系,也对本类不产生负
面影响,就放置在本类中。
谨慎使用 Serializable。实话说,这个问题会很少出现的,即使出现也会马上发现问题。是怎么回事呢?
举个例子来说,如果你使用 RMI 的方式传递一个对象 VO(Value Object) ,这个对象就必须使用 Serializable
接口,也就是把你的这个对象进行序列化,然后进行网络传输。突然有一天,客户端的 VO 对象修改了一个
属性的访问权限,从 private 变更为 public 了,如果服务器上没有做出响应的变更的话,就会报序列化失败。
这个应该属于项目管理范畴,一个类或接口客户端变更了,而服务端没有变更,那像话吗?!
第  290  页
您的设计模式
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高,其要求
的结果就是产生了大量的中转或跳转类,类只能和朋友交流,朋友少了你业务跑不起来,朋友多了,你项
目管理就复杂,大家在使用的时候做相互权衡吧。
不知道大家有没有听过这样一个理论: “任何 2 个素不相识的人中间最多只隔着 6 个人,即只用 6 个人
就可以将他们联系在一起” ,这个理论的学名叫做“六度分离” ,应用到我们项目中就是说我和我要调用的
类之间最多有 6 次传递,呵呵,这只能让大家当个乐子来看,在实际项目中你跳两次才访问到一个类估计
你就会想办法了,这也是合理的,迪米特法则要求我们类间解耦,但是解耦是有限度的,除非是计算机的
最小符号二进制的 0 和 1,那才是完全解耦,我们在实际的项目中时,需要适度的考虑这个法则,别为了套
用法则而做项目,法则只是一个参考,你跳出了这个法则,也不会有人判你刑,项目也未必会失败,这就
需要大家使用的是考虑如何度量法则了。
第  291  页
您的设计模式
26.6 开闭原则【Open Close Principle 】  
第  292  页
您的设计模式
第  293  页
您的设计模式
第 27  章 混编模式讲解
混编模式这一章节我也是思考了很久,模式的混编必须和实际的项目相关联,采用混合模式可以扬长
避短,提高项目的稳定性、扩展性、可靠性等等,其木丢就是一句话:少折腾!在客户变更了需求,你不
因为工作量暴增而沮丧;在业务临时爆发时,你不用心惊胆战的盯着看服务器的资源情况;在系统发现 BUG
的时候,你不用皱着眉头思考怎么处理淆乱的数据;这些我相信你只要做个中型项目就肯定遇到过,而且
有些问题还是灾难性的。
我这一章节的讲解尽量通过引入设计模式来完善我们的系统,本来思考了有两个讲解思路(实话说,
以前讲课,混编模式我是不讲的,这个不是一节两节课能够讲明白,你让听众稀里糊涂的还不如不讲) :一
是根据我自己的经验,把我自己做过的项目引入进来,通过亲身体验来讲解,这样效果可能会,但是也是
有缺点的,因为每个项目都有背景信息,为什么要这样设计必须把背景信息(业务需求)讲清楚,这可能
要花比较长的篇幅; 另外一种是根据现在一些开源项目来讲解, 这些开源项目都是大师的杰作, 比如 Struts,
Ibatis 等这些源码就用到了很多模式,而且有些代码非常经典,经典的会让你拍案叫绝,但是这样讲解也
是有缺陷的,代码量太大,而且由于是产品,会考虑的边缘问题非常多,需要把源码摘出来分析才可以,
需要花大量的时间,这个我已经在整理了。
好,我们就先言归正传,像根据第一种思路来讲一个案例。
第  294  页
您的设计模式
第  295  页
您的设计模式
第 28  章 更新记录:
2009 年 4 月 22 日 完成策略模式和代理模式的编写;并发布到论坛上;
2009 年 4 月 24 日 完成单例模式和多例模式的编写;
2009 年 4 月 29 日 完成工厂方法编写;
2009 年 5 月 2 日 完成抽象工厂模式的编写;
2009 年 5 月 2 日 完成了门面模式的编写;增加封面、编写计划、后序以及部分类图
2009 年 5 月 10 日 完成适配器模式的编写;
2009 年 5 月 17 日 完成模板方法模式和建造者模式;
2009 年 5 月 24 日 完成桥梁模式;
2009 年 5 月 30 日 完成命令模式和装饰模式;
2009 年 6 月 6 日 完成迭代器模式;
2009 年 6 月 7 日 完成组合模式;
2009 年 6 月 18 日 完成观察者模式;
2009 年 6 月 21 日 完成责任链模式;
2009 年 6 月 28 日 完成状态模式
2009 年 6 月 30 日 完成单一职责原则;
2009 年 7 月 4 日 完成访问者模式;
2009 年 7 月 5 日 完成迪米特法则;
2009 年 7 月 5 日 完成里氏替换法则;
第  296  页
您的设计模式
相关说明
软件环境:JDK 1.5 MyEclipse 7.0
类图设计软件:Rational Rose
博客地址:
http://hi.baidu.com/cbf4life/blog/item/e1ff58f849a8ea51242df219.html
本文下载地址:
http:// cbf4life.66ip.com/设计模式.pdf
源代码及类图下载地址:
http:// cbf4life.66ip.com /source-code.rar
论坛:
http://www.javaeye.com/topic/372233
第  297  页
您的设计模式
第 29  章 后序
首先,要说的是非常感谢大家的支持,让我有勇气续写下去,我为什么要写这本书?真的不是炫耀自
己,从 2000 年毕业,到现在 9 年的光影,在 IT 技术这块基本上都做过,从程序员起步,高级程序员,系
统分析师,项目经理,测试经理,质量经理,项目维护,IT 老师,呵呵,接触的语言也比较多,什么 C 了
汇编了 Ruby 了这些边角的东西都做过项目,更别说 Java 了,Java 项目做了 6 年,金融交易类的,OA 管理
类的,政府类的等都做过,这几年基本上都是项目经理和技术经理一块兼职,搞的很累,带过最少 3 个人
的团队,也带过 40 多人的研发团队,可以说自己比较失败,9 年了,始终还是个做技术的,不过技术还是
我最热衷的,我喜欢看简单清晰明了易懂的代码,一看代码一团糟,那这人肯定不怎么样。
9 年了,整天忙的跟疯子一样,不知道这样忙下去什么时候是个头,而且自己的年龄也不小了,不能跟
着这批 80 后甚至是 90 后一起疯狂加班了,并且最近在工作上也发生了一些事情,让我根本看不清楚未来
的路,单位是好,福利应该是同行业的中上等吧,可我们这小兵的路在何方?40 岁了还写代码,中国可行
吗?跟 90 后甚至是 00 后一起加班?这不是我想要的工作模式,很失落,所以想找个精神激励,于是就想
把自己的这几年的工作经验整理成一本书,让大家尽量能看懂的书,大家的每一个回帖、邮件、评价都给
了我莫大的鼓励!由于是第一次写书,可能确实有部分考虑不周,请大家指正。这本书能出版更好,不能
出版也无所谓,我只是想找个精神鼓励。
大家一看目录可能就发怵了,怎么是 24 个模式呀,一般书上都是 23 个模式,呵呵,确实是,我增加
了多例模式,这个一般都是融合在单例模式中讲的,我是拆出来了。
设计模式这块,我是从 04 年开始系统学习的, 《J2EE 核心模式》 、 《Java 与模式》 、 《Head First 设计模
式》等几本经典的书都拜读过,也确实学到了不少东西,但是始终觉的这些书有缺陷,前两本都是一副孔
老夫子假正经的摸样,板着脸一本正经的在讲技术,真的是研究,实话说我是忍着看下去的,技术是为了
使用服务的, 你说那么多用不到的优缺点、 场景干什么, 干嘛不说个大家接触到的看的懂例子?! 《Head First
设计模式》实话说,我不喜欢,西式的幽默不合我的胃口,看过一遍就不想看第二遍了。
这个序是我想到那里就写到那里,没有个重点,也没个理个头绪出来,大家将就吧。
2009 年 5 月 2 日
还有一个小时就又到明天了,这段时间写这本书,确实感触良多,已经一个多月过去了,进展还算可
以,至少还是比自己预期的计划提前了一些。
我看书有个坏习惯,看完书后书本就找不找着了!就图个新鲜,基本上一本书看很多遍的情况比较少
(也有,金庸的《鹿鼎记》 、 《笑傲江湖》 、 《倚天屠龙记》等看过应该不下十遍,大学的时候,同学借书了
第  298  页
您的设计模式
第  299  页
就蹭着看一遍) ,而且自己也不是很聪明的那种人,一目十行,过目不忘统统和我不沾边,我基本上属于那
种看的多记得少的人,我从中学就开始看什么四大名著了、 《初刻拍案惊奇》 、 《古文观止》还有什么《老残
游记》等等一些让人看着比较反胃的东西,基本上看过就忘,反正老爸也不管,他就看我在看书就成,记
住多少老爸也不管不问,要看书就给你买,在苦也要给我买书,这也是天下父母的一片苦心哪!做了技术
这一行以后,看书还是这个习惯,书是看了不少,看过就看过了,你要问过我这本书到底写什么,我会大
致给你描述是写 Struts 的,或是写项目管理的,或是写领域模型的,再详细的就没了,再去找那本书,也
不找不到了,不知道是借给谁了,或者当垃圾扔掉了。而且我读书也没有记笔记的习惯,看到哪里是哪里,
怎么看自己都像个懒人!
前段时间和一个师兄在争执一个问题,最后我接受了他的说法:技术上没有绝对的好与坏,一个方案
或者提议,如果你要是按照理论值来争论,那就没有个结果,就像以前有人争论过使用 delegate 的方式隔
离各个分层模块一样,有人说好,有人说不好,各说各的,确实不是很好,但是在项目中用了那就是好,
分层清晰了,开发不相互污染了,进度加快了,有什么不好的,所以大家有时候为了一个问题争的面红耳
赤的,何必呢!技术上的事,特别是一应用到项目上,懂点技术的人都想套用理论知识,特别是所谓的“砖
家” ,吓不死你不算完,好像不按他们的套路来走,项目就必定玩儿完似的!记住了那是理论,不是实际!
呵呵,这也是有感而发。
“纸上得来终觉浅,绝知此事要躬行” ,大家看书的也体会了一下这句话。
又是不知所云,记录一下心情吧!大家凑合着理解吧。
2009 年 6 月 7 日

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值