设计模式 第 2 页 目 录 1. 策略模式【Strategy Pattern】 .......................................................... 4 2. 代理模式【Proxy Pattern】 .............................................................. 8 3. 单例模式【Singleton Pattern】 ...................................................... 12 4. 多例模式【Multition Pattern】 ...................................................... 17 5. 工厂方法【Factory Method Pattern】 ............................................ 20 6. 抽象工厂模式【Abstract Factory Pattern】 ................................... 32 7. 门面模式【Facade Pattern】 .......................................................... 46 8. 适配器模式【Adapter Pattern】 .................................................... 53 9. 模板方法模式【Template Method Pattern】 ................................. 65 10. 建造者模式【Builder Pattern】 ...................................................... 84 11. 桥梁模式【Bridge Pattern】 ........................................................... 99 12. 命令模式【Command Pattern】 ................................................... 115 13. 装饰模式【Decorator Pattern】 ................................................... 129 14. 迭代器模式【Iterator Pattern】 ................................................... 141 15. 组合模式【Composite Pattern】 .................................................. 151 16. 观察者模式【Observer Pattern】 ................................................. 180 17. 访问者模式【Visitor Pattern】 ..................................................... 181 18. 状态模式【State Pattern】 ........................................................... 182 19. 责任链模式【Chain of Responsibility Pattern】 ........................... 183 您的设计模式 第 3 页 20. 原型模式【Prototype Pattern】 ................................................... 184 21. 中介者模式【Mediator Pattern】 ................................................ 185 22. 解释器模式【Interpreter Pattern】 .............................................. 186 23. 亨元模式【Flyweight Pattern】 .................................................... 187 24. 备忘录模式【Memento Pattern】 ............................................... 188 25. 模式大PK ...................................................................................... 189 26. 混编模式讲解 ............................................................................... 190 27. 更新记录: ................................................................................... 191 28. 相关说明 ....................................................................................... 192 29. 后序 ............................................................................................... 193 您的设计模式 第 4 页 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 { //每个锦囊妙计都是一个可执行的算法 您的设计模式 第 5 页 public void operate(); } 然后再写三个实现类,有三个妙计嘛: 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. * 孙夫人断后,挡住追兵 */ 您的设计模式 第 6 页 public class BlockEnemy 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 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 { /** * 赵云出场了,他根据诸葛亮给他的交代,依次拆开妙计 您的设计模式 第 7 页 */ public static void main(String[] args) { Context context; //刚刚到吴国的时候拆第一个 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 就可以了,这个不多说了,自己领会吧。 您的设计模式 第 8 页 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 { 您的设计模式 第 9 页 public void happyWithMan() { System.out.println('潘金莲在和男人做那个.....'); } 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(); //王婆这么大年龄了,谁看她抛 媚眼?! } 您的设计模式 第 10 页 } 两个女主角都上场了,男主角也该出现了: 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. 您的设计模式 第 11 页 */ public class JiaShi implements KindWomen { public void happyWithMan() { System.out.println('贾氏正在Happy中......'); } 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 的多态,干活的是被代理类, 代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代 理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口 呗。 您的设计模式 第 12 页 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(){ //世俗和道德约束你,目的就是不让你产生第二个皇帝 } 您的设计模式 第 13 页 public static Emperor getInstance(){ 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(); //三天见的皇帝都是同一个人,荣幸吧! } 您的设计模式 第 14 页 } 看到没,大臣天天见到的都是同一个皇帝,不会产生错乱情况,反正都是一个皇帝,是 好是坏就这一个,只要提到皇帝,大家都知道指的是谁,清晰,而又明确。问题是这是通常 情况,还有个例的,如同一个时期同一个朝代有两个皇帝,怎么办? 单例模式很简单,就是在构造函数中多了加一个构造函数,访问权限是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 您的设计模式 第 15 页 微秒,就在这0.001 微秒之内,线程B 执行到if(this.singletonPattern == null),你说 这个时候这个判断条件是true 还是false?是true,那然后呢?线程B 也往下走,于是乎 就在内存中就有两个SingletonPattern 的实例了,看看是不是出问题了?如果你这个单例 是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!数据一致性校 验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上 你是重现不了的,不寒而栗吧,那怎么修改?有很多种方案,我就说一种,能简单的、彻底 解决问题的方案: 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 ()直接返回给你,解决问题! 您的设计模式 第 16 页 您的设计模式 第 17 页 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出一个皇帝出来。 * 问题出来了:如果真在一个时间,中国出现了两个皇帝怎么办?比如明朝土木堡之变后, * 明英宗被俘虏,明景帝即位,但是明景帝当上皇帝后乐疯了,竟然忘记把他老哥明英宗 您的设计模式 第 18 页 削为太上皇, * 也就是在这一个多月的时间内,中国竟然有两个皇帝! * */ @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 } } //就这么多皇帝了,不允许再推举一个皇帝(new 一个皇帝) private Emperor(){ //世俗和道德约束你,目的就是不让你产生第二个皇帝 } private Emperor(String info){ emperorInfoList.add(info); } public static Emperor getInstance(){ Random random = new Random(); countNumOfEmperor = random.nextInt(maxNumOfEmperor); //随机 拉出一个皇帝,只要是个精神领袖就成 return (Emperor)emperorList.get(countNumOfEmperor); } //皇帝叫什么名字呀 public static void emperorInfo(){ System.out.println(emperorInfoList.get(countNumOfEmperor)); } } 您的设计模式 第 19 页 那大臣是比较悲惨了,两个皇帝呀,两个老子呀,怎么拜呀,不管了,只要是个皇帝就 成: package com.cbf4life.singleton2; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 大臣们悲惨了,一个皇帝都伺候不过来了,现在还来了两个个皇帝 * TND,不管了,找到个皇帝,磕头,请按就成了! */ @SuppressWarnings('all') public class Minister { /** * @param args */ public static void main(String[] args) { int ministerNum =10; //10个大臣 for(int i=0;i System.out.print('第'+(i+1)+'个大臣参拜的是:'); emperor.emperorInfo(); } } } 那各位看官就可能会不屑了:有的大臣可是有骨气,只拜一个真神,你怎么处理?这个 问题太简单,懒的详细回答你,getInstance(param)是不是就解决了这个问题?!自己思考, 太Easy 了。 您的设计模式 第 20 页 5. 工厂方法【Factory Method Pattern】 女娲补天的故事大家都听说过吧,今天不说这个,说女娲创造人的故事,可不是“造人” 的工作,这个词被现代人滥用了。这个故事是说,女娲在补了天后,下到凡间一看,哇塞, 风景太优美了,天空是湛蓝的,水是清澈的,空气是清新的,太美丽了,然后就待时间长了 就有点寂寞了,没有动物,这些看的到都是静态的东西呀,怎么办? 别忘了是神仙呀,没有办不到的事情,于是女娲就架起了八卦炉(技术术语:建立工厂) 开始创建人,具体过程是这样的:先是泥巴捏,然后放八卦炉里烤,再扔到地上成长,但是 意外总是会产生的: 第一次烤泥人,兹兹兹兹~~,感觉应该熟了,往地上一扔,biu~,一个白人诞生了,没 烤熟! 第二次烤泥人,兹兹兹兹兹兹兹兹~~,上次都没烤熟,这次多烤会儿,往地上一扔,嘿, 熟过头了,黑人哪! 第三次烤泥人,兹~兹~兹~,一边烤一边看着,嘿,正正好,Perfect,优品,黄色人种! 【备注:RB 人不再此列】 这个过程还是比较有意思的,先看看类图:(之前在论坛上有兄弟建议加类图和源文件, 以后的模式都会加上去,之前的会一个一个的补充,目的是让大家看着舒服,看着愉悦,看 着就想要,就像是看小说一样,目标,目标而已,能不能实现就看大家给我的信心了) 您的设计模式 第 21 页 那这个过程我们就用程序来表现,首先定义一个人类的总称: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 定义一个人类的统称 */ public interface Human { //首先定义什么是人类 //人是愉快的,会笑的,本来是想用smile表示,想了一下laugh更合适,好长时间 没有大笑了; public void laugh(); //人类还会哭,代表痛苦 public void cry(); //人类会说话 public void talk(); } 然后定义具体的人种: 您的设计模式 第 22 页 package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 黄色人种,这个翻译的不准确,将就点吧 */ public class YellowHuman implements Human { public void cry() { System.out.println('黄色人种会哭'); } public void laugh() { System.out.println('黄色人种会大笑,幸福呀!'); } public void talk() { System.out.println('黄色人种会说话,一般说的都是双字节'); } } 白色人种: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 白色人种 */ public class WhiteHuman implements Human { public void cry() { System.out.println('白色人种会哭'); } public void laugh() { System.out.println('白色人种会大笑,侵略的笑声'); 您的设计模式 第 23 页 } public void talk() { System.out.println('白色人种会说话,一般都是但是单字节!'); } } 黑色人种: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 黑色人种,记得中学学英语,老师说black man是侮辱人的意思,不懂,没跟老外说 话 */ public class BlackHuman implements Human { public void cry() { System.out.println('黑人会哭'); } public void laugh() { System.out.println('黑人会笑'); } public void talk() { System.out.println('黑人可以说话,一般人听不懂'); } } 人种也定义完毕了,那我们把八卦炉定义出来: package com.cbf4life; import java.util.List; import java.util.Random; /** * @author cbf4Life cbf4life@126.com 您的设计模式 第 24 页 * I'm glad to share my knowledge with you all. * 今天讲女娲造人的故事,这个故事梗概是这样的: * 很久很久以前,盘古开辟了天地,用身躯造出日月星辰、山川草木,天地一片繁华 * One day,女娲下界走了一遭,哎!太寂寞,太孤独了,没个会笑的、会哭的、会说话 的东东 * 那怎么办呢?不用愁,女娲,神仙呀,造出来呀,然后捏泥巴,放八卦炉(后来这个成 了太白金星的宝贝)中烤,于是就有了人: * 我们把这个生产人的过程用Java程序表现出来: */ public class HumanFactory { //定一个烤箱,泥巴塞进去,人就出来,这个太先进了 public static Human createHuman(Class c){ Human human=null; //定义一个类型的人类 try { human = (Human)Class.forName(c.getName()).newInstance(); //产生一个人种 } catch (InstantiationException e) {//你要是不说个人种颜色的话, 没法烤,要白的黑,你说话了才好烤 System.out.println('必须指定人种的颜色'); } catch (IllegalAccessException e) { //定义的人种有问题,那就烤 不出来了,这是... System.out.println('人种定义错误!'); } catch (ClassNotFoundException e) { //你随便说个人种,我到哪里 给你制造去?! System.out.println('混蛋,你指定的人种找不到!'); } return human; } } 然后我们再把女娲声明出来: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. 您的设计模式 第 25 页 * 首先定义女娲,这真是额的神呀 */ public class NvWa { public static void main(String[] args) { //女娲第一次造人,试验性质,少造点,火候不足,缺陷产品 System.out.println('------------造出的第一批人是这样的:白人 -----------------'); Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class); whiteHuman.cry(); whiteHuman.laugh(); whiteHuman.talk(); //女娲第二次造人,火候加足点,然后又出了个次品,黑人 System.out.println('\n\n------------造出的第二批人是这样的:黑人 -----------------'); Human blackHuman = HumanFactory.createHuman(BlackHuman.class); blackHuman.cry(); blackHuman.laugh(); blackHuman.talk(); //第三批人了,这次火候掌握的正好,黄色人种(不写黄人,免得引起歧义), 备注:RB人不属于此列 System.out.println('\n\n------------造出的第三批人是这样的:黄色 人种-----------------'); Human yellowHuman = HumanFactory.createHuman(YellowHuman.class); yellowHuman.cry(); yellowHuman.laugh(); yellowHuman.talk() } } 这样这个世界就热闹起来了,人也有了,但是这样创建太累了,神仙也会累的,那怎么 办?神仙就想了:我塞进去一团泥巴,随机出来一群人,管他是黑人、白人、黄人,只要是 人就成(你看看,神仙都偷懒,何况是我们人),先修改类图: 您的设计模式 第 26 页 然后看我们的程序修改,先修改HumanFactory.java,增加了createHuman()方法: package com.cbf4life; import java.util.List; import java.util.Random; public class HumanFactory { //定一个烤箱,泥巴塞进去,人就出来,这个太先进了 public static Human createHuman(Class c){ Human human=null; //定义一个类型的人类 try { human = (Human)Class.forName(c.getName()).newInstance(); //产生一个人种 } catch (InstantiationException e) {//你要是不说个人种颜色的话, 没法烤,要白的黑,你说话了才好烤 System.out.println('必须指定人种的颜色'); } catch (IllegalAccessException e) { //定义的人种有问题,那就烤 不出来了,这是... System.out.println('人种定义错误!'); } catch (ClassNotFoundException e) { //你随便说个人种,我到哪里 给你制造去?! 您的设计模式 第 27 页 System.out.println('混蛋,你指定的人种找不到!'); } return human; } //女娲生气了,把一团泥巴塞到八卦炉,哎产生啥人种就啥人种 public static Human createHuman(){ Human human=null; //定义一个类型的人类 //首先是获得有多少个实现类,多少个人种 List ClassUtils.getAllClassByInterface(Human.class); //定义了多少人种 //八卦炉自己开始想烧出什么人就什么人 Random random = new Random(); int rand = random.nextInt(concreteHumanList.size()); human = createHuman(concreteHumanList.get(rand)); return human; } } 然后看女娲是如何做的: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 首先定义女娲,这真是额的神呀 */ public class NvWa { public static void main(String[] args) { //女娲第一次造人,试验性质,少造点,火候不足,缺陷产品 System.out.println('------------造出的第一批人是这样的:白人 -----------------'); Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class); whiteHuman.cry(); whiteHuman.laugh(); whiteHuman.talk(); 您的设计模式 第 28 页 //女娲第二次造人,火候加足点,然后又出了个次品,黑人 System.out.println('\n\n------------造出的第二批人是这样的:黑人 -----------------'); Human blackHuman = HumanFactory.createHuman(BlackHuman.class); blackHuman.cry(); blackHuman.laugh(); blackHuman.talk(); //第三批人了,这次火候掌握的正好,黄色人种(不写黄人,免得引起歧义), 备注:RB人不属于此列 System.out.println('\n\n------------造出的第三批人是这样的:黄色 人种-----------------'); Human yellowHuman = HumanFactory.createHuman(YellowHuman.class); yellowHuman.cry(); yellowHuman.laugh(); yellowHuman.talk(); //女娲烦躁了,爱是啥人种就是啥人种,烧吧 for(int i=0;i<10000000000;i++){ System.out.println('\n\n------------随机产生人种了 -----------------' + i); Human human = HumanFactory.createHuman(); human.cry(); human.laugh(); human.talk(); } } } 哇,这个世界热闹了!,不过还没有完毕,这个程序你跑不起来,还要有这个类: package com.cbf4life; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; 您的设计模式 第 29 页 import java.util.List; /** * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you * all. * */ @SuppressWarnings('all') public class ClassUtils { //给一个接口,返回这个接口的所有实现类 public static List List 回结果 //如果不是一个接口,则不做处理 if(c.isInterface()){ String packageName = c.getPackage().getName(); //获得当 前的包名 try { List 获得当前包下以及子包下的所有类 //判断是否是同一个接口 for(int i=0;i 是不是一个接口 if(!c.equals(allClass.get(i))){ //本身不加进 去 returnClassList.add(allClass.get(i)); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return returnClassList; } 您的设计模式 第 30 页 //从一个包中查找出所有的类,在jar包中不能查找 private static List throws ClassNotFoundException, IOException { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); String path = packageName.replace('.', '/'); Enumeration List while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } ArrayList for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes; } private static List packageName) throws ClassNotFoundException { List if (!directory.exists()) { return classes; } File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains('.'); classes.addAll(findClasses(file, packageName + '.' + file.getName())); } else if (file.getName().endsWith('.class')) { classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6))); } } return classes; } } 告诉你了,这个ClassUtils 可是个宝,用处可大了去了,可以由一个接口查找到所有 您的设计模式 第 31 页 的实现类,也可以由父类查找到所有的子类,这个要自己修改一下,动脑筋想下,简单的很! 完整的类图如下: 我们来总结一下,特别是增加了createHuman()后,是不是这个工厂的扩展性更好了? 你看你要再加一个人种,只要你继续集成Human 接口成了,然后啥都不用修改就可以生产了, 具体产多少,那要八卦炉说了算,简单工厂模式就是这么简单,那我们再引入一个问题:人 是有性别的呀,有男有女,你这怎么没区别,别急,这个且听下回分解! 您的设计模式 第 32 页 6. 抽象工厂模式【Abstract Factory Pattern】 好了,我们继续上一节课,上一节讲到女娲造人,人是造出来了,世界时热闹了,可是 低头一看,都是清一色的类型,缺少关爱、仇恨、喜怒哀乐等情绪,人类的生命太平淡了, 女娲一想,猛然一拍脑袋,Shit!忘记给人类定义性别了,那怎么办?抹掉重来,然后就把 人类重新洗牌,准备重新开始制造人类。 由于先前的工作已经花费了很大的精力做为铺垫,也不想从头开始了,那先说人类 (Product 产品类)怎么改吧,好,有了,给每个人类都加一个性别,然后再重新制造,这 个问题解决了,那八卦炉怎么办?只有一个呀,要么生产出全都是男性,要不都是女性,那 不行呀,有了,把已经有了一条生产线——八卦炉(工厂模式中的Concrete Factory)拆 开,于是女娲就使用了“八卦拷贝术”,把原先的八卦炉一个变两个,并且略加修改,就成 了女性八卦炉(只生产女性,一个具体工厂的实现类)和男性八卦炉(只生产男性,又一个 具体工厂的实现类),这个过程的类图如下: 先看人类(也就是产品)的类图: 这个类图也比较简单,Java 的典型类图,一个接口,几个抽象类,然后是几个实现类, 没啥多说的,其中三个抽象类在抽象工厂模式中是叫做产品等级,六个实现类是叫做产品族, 这个也比较好理解,实现类嘛是真实的产品,一个叫产品,多了就叫产品族,然后再看工厂 类: 您的设计模式 第 33 页 其中抽象工厂只实现了一个createHuman 的方法,目的是简化实现类的代码工作量,这 个在讲代码的时候会说。这里还使用了Jdk 1.5 的一个新特性Enum 类型,其实这个完全可 以类的静态变量来实现,但我想既然是学习就应该学有所获得,即使你对这个模式非常了解, 也可能没用过Enum 类型,也算是一个不同的知识点吧,我希望给大家讲解,每次都有新的 技术点提出来,每个人都会有一点的收获就足够了,然后在具体的项目中使用时,知道有这 个技术点,然后上baidu 狗狗一下就能解决问题。话题扯远了,我们继续类图,完整的类图 如下,这个看不大清楚,其实就是上面那两个类图加起来,大家可以看源码中的那个类图文 件: 然后类图讲解完毕,我们来看程序实现: 您的设计模式 第 34 页 package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 定义一个人类的统称,问题出来了,刚刚定义的时候忘记定义性别了 * 这个重要的问题非修改不可,否则这个世界上太多太多的东西不存在了 */ public interface Human { //首先定义什么是人类 //人是愉快的,会笑的,本来是想用smile表示,想了一下laugh更合适,好长时间 没有大笑了; public void laugh(); //人类还会哭,代表痛苦 public void cry(); //人类会说话 public void talk(); //定义性别 public void sex(); } 人类的接口定义好,然后根据接口创建三个抽象类,也就是三个产品等级,实现 laugh()、cry()、talk()三个方法,以AbstractYellowHuman 为例: package com.cbf4life.yellowHuman; import com.cbf4life.Human; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 为什么要修改成抽象类呢?要定义性别呀 */ public abstract class AbstractYellowHuman implements Human { public void cry() { System.out.println('黄色人种会哭'); 您的设计模式 第 35 页 } public void laugh() { System.out.println('黄色人种会大笑,幸福呀!'); } public void talk() { System.out.println('黄色人种会说话,一般说的都是双字节'); } } 其他的两个抽象类AbstractWhiteHuman 和AbstractgBlackHuman 与此类似的事项方法, 不再通篇拷贝代码,大家可以看一下源代码。算了,还是拷贝,反正是电子档的,不想看就 往下翻页,也成就了部分“懒人”,不用启动Eclipse,还要把源码拷贝进来: 白种人的抽象类: package com.cbf4life.whiteHuman; import com.cbf4life.Human; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 白色人人种 * 为了代码整洁,新建一个包,这里是白种人的天下了 */ public abstract class AbstractWhiteHuman implements Human { public void cry() { System.out.println('白色人种会哭'); } public void laugh() { System.out.println('白色人种会大笑,侵略的笑声'); 您的设计模式 第 36 页 } public void talk() { System.out.println('白色人种会说话,一般都是但是单字节!'); } } 黑种人的抽象类: package com.cbf4life.blackHuman; import com.cbf4life.Human; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 黑色人种,记得中学学英语,老师说black man是侮辱人的意思,不懂,没跟老外说 话 */ public abstract class AbstractBlackHuman implements Human { public void cry() { System.out.println('黑人会哭'); } public void laugh() { System.out.println('黑人会笑'); } public void talk() { System.out.println('黑人可以说话,一般人听不懂'); } } 三个抽象类都实现完毕了,然后就是些实现类了。其实,你说抽象类放这里有什么意义 吗?就是不允许你new 出来一个抽象的对象呗,使用非抽象类完全就可以代替,呵呵,杀猪 杀尾巴,各有各的杀法,不过既然进了Java 这个门就要遵守Java 这个规矩,我们看实现类: 您的设计模式 第 37 页 女性黄种人的实现类: package com.cbf4life.yellowHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 女性黄种人 */ public class YellowFemaleHuman extends AbstractYellowHuman { public void sex() { System.out.println('该黄种人的性别为女...'); } } 男性黄种人的实现类: package com.cbf4life.yellowHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 男性黄种人 */ public class YellowMaleHuman extends AbstractYellowHuman { public void sex() { System.out.println('该黄种人的性别为男....'); } } 女性白种人的实现类: package com.cbf4life.whiteHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all.\ * 女性白种人 */ public class WhiteFemaleHuman extends AbstractWhiteHuman { 您的设计模式 第 38 页 public void sex() { System.out.println('该白种人的性别为女....'); } } 男性白种人的实现类: package com.cbf4life.whiteHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 男性白种人 */ public class WhiteMaleHuman extends AbstractWhiteHuman { public void sex() { System.out.println('该白种人的性别为男....'); } } 女性黑种人的实现类: package com.cbf4life.blackHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 女性黑种人 */ public class BlackFemaleHuman extends AbstractBlackHuman { public void sex() { System.out.println('该黑种人的性别为女...'); } } 您的设计模式 第 39 页 男性黑种人的实现类: package com.cbf4life.blackHuman; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 男性黑种人 */ public class BlackMaleHuman extends AbstractBlackHuman { public void sex() { System.out.println('该黑种人的性别为男...'); } } 抽象工厂模式下的产品等级和产品族都已经完成,也就是人类以及产生出的人类是什么 样子的都已经定义好了,下一步就等着工厂开工创建了,那我们来看工厂类。 在看工厂类之前我们先看那个枚举类型,这个是很有意思的: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 世界上有哪些类型的人,列出来 * JDK 1.5开始引入enum类型也是目的的,吸引C程序员转过来 */ public enum HumanEnum { //把世界上所有人类型都定义出来 YelloMaleHuman('com.cbf4life.yellowHuman.YellowMaleHuman'), YelloFemaleHuman('com.cbf4life.yellowHuman.YellowFemaleHuman') , WhiteFemaleHuman('com.cbf4life.whiteHuman.WhiteFemaleHuman'), WhiteMaleHuman('com.cbf4life.whiteHuman.WhiteMaleHuman'), BlackFemaleHuman('com.cbf4life.blackHuman.BlackFemaleHuman'), 您的设计模式 第 40 页 BlackMaleHuman('com.cbf4life.blackHuman.BlackMaleHuman'); private String value = ''; //定义构造函数,目的是Data(value)类型的相匹配 private HumanEnum(String value){ this.value = value; } public String getValue(){ return this.value; } /* * java enum类型尽量简单使用,尽量不要使用多态、继承等方法 * 毕竟用Clas完全可以代替enum */ } 我之所以引入Enum 这个类型,是想让大家在看这本书的时候能够随时随地的学到点什 么,你如果看不懂设计模式,你可以从我的程序中学到一些新的技术点,不用像我以前报着 砖头似的书在那里啃,看一遍不懂,再看第二遍,然后翻了英文原本才知道,哦~,原来是 这样滴,只能说有些翻译家实在不懂技术。我在讲解技术的时候,尽量少用专业术语,尽量 使用大部分人类都能理解的语言。 Enum 以前我也很少用,最近在一个项目中偶然使用上了,然后才发觉它的好处,Enum 类型作为一个参数传递到一个方法中时,在Junit 进行单元测试的时候,不用判断输入参数 是否为空、长度为0 的边界异常条件,如果方法传入的参数不是Enum 类型的话,根本就传 递不进来,你说定义一个类,定义一堆的静态变量,这也可以呀,这个不和你抬杠,上面的 代码我解释一下,构造函数没啥好说的,然后是getValue()方法,就是获得枚举类型中一 个元素的值,枚举类型中的元素也是有名称和值的,这个和HashMap 有点类似。 然后,我们看我们的工厂类,先看接口: package com.cbf4life; 您的设计模式 第 41 页 /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 这次定一个接口,应该要造不同性别的人,需要不同的生产线 * 那这个八卦炉必须可以制造男人和女人 */ public interface HumanFactory { //制造黄色人种 public Human createYellowHuman(); //制造一个白色人种 public Human createWhiteHuman(); //制造一个黑色人种 public Human createBlackHuman(); } 然后看抽象类: package com.cbf4life.humanFactory; import com.cbf4life.Human; import com.cbf4life.HumanEnum; import com.cbf4life.HumanFactory; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 编写一个抽象类,根据enum创建一个人类出来 */ public abstract class AbstractHumanFactory implements HumanFactory { /* * 给定一个性别人种,创建一个人类出来专业术语是产生产品等级 */ protected Human createHuman(HumanEnum humanEnum) { Human human = null; //如果传递进来不是一个Enum中具体的一个Element的话,则不处理 if (!humanEnum.getValue().equals('')) { try { //直接产生一个实例 您的设计模式 第 42 页 human = (Human) Class.forName(humanEnum.getValue()).newInstance(); } catch (Exception e) { //因为使用了enum,这个种异常情况不会产生了,除非你的enum有问 题; e.printStackTrace(); } } return human; } } 看到没,这就是引入enum 的好处,createHuman(HumanEnum humanEnum)这个方法定义 了输入参数必须是HumanEnum 类型,然后直接使用humanEnum.getValue()方法就能获得具 体传递进来的值,这个不多说了,大家自己看程序领会,没多大难度,这个抽象类的目的就 是减少下边实现类的代码量,我们看实现类: 男性工厂,只创建男性: package com.cbf4life.humanFactory; import com.cbf4life.Human; import com.cbf4life.HumanEnum; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 男性创建工厂 */ public class MaleHumanFactory extends AbstractHumanFactory { //创建一个男性黑种人 public Human createBlackHuman() { return super.createHuman(HumanEnum.BlackMaleHuman); } //创建一个男性白种人 public Human createWhiteHuman() { return super.createHuman(HumanEnum.WhiteMaleHuman); } //创建一个男性黄种人 您的设计模式 第 43 页 public Human createYellowHuman() { return super.createHuman(HumanEnum.YelloMaleHuman); } } 女性工厂,只创建女性: package com.cbf4life.humanFactory; import com.cbf4life.Human; import com.cbf4life.HumanEnum; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all.\ * 女性创建工厂 */ public class FemaleHumanFactory extends AbstractHumanFactory { //创建一个女性黑种人 public Human createBlackHuman() { return super.createHuman(HumanEnum.BlackFemaleHuman); } //创建一个女性白种人 public Human createWhiteHuman() { return super.createHuman(HumanEnum.WhiteFemaleHuman); } //创建一个女性黄种人 public Human createYellowHuman() { return super.createHuman(HumanEnum.YelloFemaleHuman); } } 产品定义好了,工厂也定义好了,万事俱备只欠东风,那咱就开始造吧,哦,不对,女 娲开始造人了: package com.cbf4life; 您的设计模式 第 44 页 import com.cbf4life.humanFactory.FemaleHumanFactory; import com.cbf4life.humanFactory.MaleHumanFactory; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 女娲建立起了两条生产线,分别是: * 男性生产线 * 女性生产线 */ public class NvWa { public static void main(String[] args) { //第一条生产线,男性生产线 HumanFactory maleHumanFactory = new MaleHumanFactory(); //第二条生产线,女性生产线 HumanFactory femaleHumanFactory = new FemaleHumanFactory(); //生产线建立完毕,开始生产人了: Human maleYellowHuman = maleHumanFactory.createYellowHuman(); Human femaleYellowHuman = femaleHumanFactory.createYellowHuman(); maleYellowHuman.cry(); maleYellowHuman.laugh(); femaleYellowHuman.sex(); /* * ..... * 后面你可以续了 */ } } 两个八卦炉,一个造女的,一个造男的,开足马力,一直造到这个世界到现在这个模式 为止。 抽象工厂模式讲完了,那我们再思考一些问题:工厂模式有哪些优缺点?先说优点,我 这人一般先看人优点,非常重要的有点就是,工厂模式符合OCP 原则,也就是开闭原则,怎 么说呢,比如就性别的问题,这个世界上还存在双性人,是男也是女的人,那这个就是要在 您的设计模式 第 45 页 我们的产品族中增加一类产品,同时再增加一个工厂就可以解决这个问题,不需要我再来实 现了吧,很简单的大家自己画下类图,然后实现下。 那还有没有其他好处呢?抽象工厂模式,还有一个非常大的有点,高内聚,低耦合,在 一个较大的项目组,产品是由一批人定义开发的,但是提供其他成员访问的时候,只有工厂 方法和产品的接口,也就是说只需要提供Product Interface 和Concrete Factory 就可以 产生自己需要的对象和方法,Java 的高内聚低耦合的特性表现的一览无遗,哈哈。 您的设计模式 第 46 页 7. 门面模式【Facade Pattern】 好,我们继续讲课。大家都是高智商的人,都写过纸质的信件吧,比如给女朋友写情书 什么的,写信的过程大家都还记得吧,先写信的内容,然后写信封,然后把信放到信封中, 封好,投递到信箱中进行邮递,这个过程还是比较简单的,虽然简单,这四个步骤都是要跑 的呀,信多了还是麻烦,比如到了情人节,为了大海捞针,给十个女孩子发情书,都要这样 跑一遍,你不要累死,更别说你要发个广告信啥的,一下子发1 千万封邮件,那不就完蛋了? 那怎么办呢?还好,现在邮局开发了一个新业务,你只要把信件的必要信息高速我,我给你 发,我来做这四个过程,你就不要管了,只要把信件交给我就成了。 我们的类图还是从最原始的状态开始: 在这中环境下,最累的是写信的人,为了发送一封信出去要有四个步骤,而且这四个步 骤还不能颠倒,你不可能没写信就把信放到信封吧,写信的人要知道这四个步骤,而且还要 知道这四个步骤的顺序,恐怖吧,我们先看看这个过程如何表现出来的: 先看写信的过程接口,定义了写信的四个步骤: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 定义一个写信的过程 您的设计模式 第 47 页 */ public interface LetterProcess { //首先要写信的内容 public void writeContext(String context); //其次写信封 public void fillEnvelope(String address); //把信放到信封里 public void letterInotoEnvelope(); //然后邮递 public void sendLetter(); } 写信过程的具体实现: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 写信的具体实现了 */ public class LetterProcessImpl implements LetterProcess { //写信 public void writeContext(String context) { System.out.println('填写信的内容....' + context); } //在信封上填写必要的信息 public void fillEnvelope(String address) { System.out.println('填写收件人地址及姓名....' + address); } //把信放到信封中,并封好 public void letterInotoEnvelope() { System.out.println('把信放到信封中....'); } //塞到邮箱中,邮递 您的设计模式 第 48 页 public void sendLetter() { System.out.println('邮递信件...'); } } 然后就有人开始用这个过程写信了: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 我开始给朋友写信了 */ public class Client { public static void main(String[] args) { //创建一个处理信件的过程 LetterProcess letterProcess = new LetterProcessImpl(); //开始写信 letterProcess.writeContext('Hello,It's me,do you know who I am? I'm your old lover. I'd like to....'); //开始写信封 letterProcess.fillEnvelope('Happy Road No. 666,God Province,Heaven'); //把信放到信封里,并封装好 letterProcess.letterInotoEnvelope(); //跑到邮局把信塞到邮箱,投递 letterProcess.sendLetter(); } } 那这个过程与高内聚的要求相差甚远,你想,你要知道这四个步骤,而且还要知道这四 个步骤的顺序,一旦出错,信就不可能邮寄出去,那我们如何来改进呢?先看类图: 您的设计模式 第 49 页 这就是门面模式,还是比较简单的,Sub System 比较复杂,为了让调用者更方便的调 用,就对Sub System 进行了封装,增加了一个门面,Client 调用时,直接调用门面的方法 就可以了,不用了解具体的实现方法以及相关的业务顺序,我们来看程序的改变, LetterProcess 接口和实现类都没有改变,只是增加了一个ModenPostOffice 类,我们这个 java 程序清单如下: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. */ public class ModenPostOffice { private LetterProcess letterProcess = new LetterProcessImpl(); //写信,封装,投递,一体化了 public void sendLetter(String context,String address){ //帮你写信 letterProcess.writeContext(context); //写好信封 letterProcess.fillEnvelope(address); //把信放到信封中 letterProcess.letterInotoEnvelope(); 您的设计模式 第 50 页 //邮递信件 letterProcess.sendLetter(); } } 这个类是什么意思呢,就是说现在又一个叫Hell Road PostOffice(地狱路邮局)提 供了一种新型的服务,客户只要把信的内容以及收信地址给他们,他们就会把信写好,封好, 并发送出去,这种服务提出时大受欢迎呀,这简单呀,客户减少了很多工作,那我们看看客 户是怎么调用的,Client.java 的程序清单如下: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 我开始给朋友写信了 */ public class Client { public static void main(String[] args) { //现代化的邮局,有这项服务,邮局名称叫Hell Road ModenPostOffice hellRoadPostOffice = new ModenPostOffice(); //你只要把信的内容和收信人地址给他,他会帮你完成一系列的工作; String address = 'Happy Road No. 666,God Province,Heaven'; // 定义一个地址 String context = 'Hello,It's me,do you know who I am? I'm your old lover. I'd like to....'; hellRoadPostOffice.sendLetter(context, address); } } 看到没,客户简单了很多,提供这种模式后,系统的扩展性也有了很大的提高,突然一 个非常时期,寄往God Province(上帝省)的邮件都必须进行安全检查,那我们这个就很 好处理了,看类图: 您的设计模式 第 51 页 看这个红色的框,只增加了这一部分,其他部分在类图上都不需要改动,那我们来看源 码: package com.cbf4life.facade; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. */ public class ModenPostOffice { private LetterProcess letterProcess = new LetterProcessImpl(); private Police letterPolice = new Police(); //写信,封装,投递,一体化了 public void sendLetter(String context,String address){ //帮你写信 letterProcess.writeContext(context); //写好信封 letterProcess.fillEnvelope(address); //警察要检查信件了 letterPolice.checkLetter(letterProcess); //把信放到信封中 letterProcess.letterInotoEnvelope(); 您的设计模式 第 52 页 //邮递信件 letterProcess.sendLetter(); } } 只是增加了一个letterPolice 变量的声明以及一个方法的调用,那这个写信的过程就 变成了这样:先写信,然后写信封,然后警察开始检查,然后才把信放到信封,然后发送出 去,那这个变更对客户来说,是透明的,他根本就看不到有人在检查他的邮件,他也不用了 解,反正现代化的邮件都帮他做了,这也是他乐意的地方。 门面模式讲解完毕,这是一个很好的封装方法,一个子系统比较复杂的实话,比如算法 或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常 好。还有,在一个较大项目中的时候,为了避免人员带来的风险,也可以使用这个模式,技 术水平比较差的成员,尽量安排独立的模块(Sub System),然后把他写的程序封装到一个 门面里,尽量让其他项目成员不用看到这些烂人的代码,看也看不懂,我也遇到过一个“高 人”写的代码,private 方法、构造函数、常量基本都不用,你要一个public 方法,好, 一个类里就一个public 方法,所有代码都在里面,然后你就看吧,一大坨的程序,看着能 把人逼疯,使用门面模式后,对门面进行单元测试,约束项目成员的代码质量,对项目整体 质量的提升也是一个比较好的帮助。 您的设计模式 第 53 页 8. 适配器模式【Adapter Pattern】 好,请安静,后排聊天的同学别吵醒前排睡觉的同学了,大家要相互理解嘛。今天讲适 配器模式,这个模式也很简单,你笔记本上的那个拖在外面的黑盒子就是个适配器,一般你 在中国能用,在日本也能用,虽然两个国家的的电源电压不同,中国是220V,日本是110V, 但是这个适配器能够把这些不同的电压转换为你需要的36V 电压,保证你的笔记本能够正常 运行,那我们在设计模式中引入这个适配器模式是不是也是这个意思呢?是的,一样的作用, 两个不同接口,有不同的实现,但是某一天突然上帝命令你把B 接口转换为A 接口,怎么办? 继承,能解决,但是比较傻,而且还违背了OCP 原则,怎么办?好在我们还有适配器模式。 适配器的通用类图是这个样子滴: 首先声明,这个不是我画的,这是从Rational Rose 的帮助文件中截取的,这个类图也 很容易理解,Target 是一个类(接口),Adaptee 是一个接口,通过Adapter 把Adaptee 包 装成Target 的一个子类(实现类),注意了这里用了个名词包装(Wrapper),那其实这个模 式也叫做包装模式(Wrapper),但是包装模式可不知一个,还包括了以后要讲的装饰模式。 我来讲个自己的一个经验,让大家可以更容易了解这个适配器模式,否则纯讲技术太枯燥了, 技术也可以有娱乐的嘛。 我在2004 年的时候带了一个项目,做一个人力资源管理,该项目是我们总公司发起的 项目,公司一共有700 多号人,包括子公司,这个项目还是比较简单的,分为三大模块:人 员信息管理,薪酬管理,职位管理,其中人员管理这块就用到了适配器模式,是怎么回事呢? 当时开发时明确的指明:人员信息简管理的对象是所有员工的所有信息,然后我们就这样设 您的设计模式 第 54 页 计了一个类图: 还是比较简单的,有一个对象UserInfo 存储用户的所有信息(实际系统上还有很多子 类,不多说了),也就是BO(Business Object),这个对象设计为贫血对象(Thin Business Object),不需要存储状态以及相关的关系,而且我也是反对使用充血对象(Rich Business Object),这里说了两个名词贫血对象和充血对象,这两个名词很简单,在领域模型中分别 叫做贫血领域模型和充血领域模型,有什么区别呢?在一个对象中不存储实体状态以及对象 之间的关系的就叫做贫血对象,上升到领域模型中就是贫血领域模型,有实体状态和对象关 系的模型的就是充血领域模型,是不是太技术化了?这个看不懂没关系,都是糊弄人的东西, 属于专用名词,这本书写完了,我再折腾本领域模型的文章,揭露领域模型中糊弄人的专有 名词,这个绝对是专有名词的堆砌,呵呵。扯远了,我们继续说适配器模式,这个UserInfo 对象,在系统中很多地方使用,你可以查看自己的信息,也可以做修改,当然这个对象是有 setter 方法的,我们这里用不到就隐藏掉了。 这个项目是04 年年底投产的,运行到05 年年底还是比较平稳的,中间修修补补也很正 常,05 年年底不知道是那股风吹的,很多公司开始使用借聘人员的方式招聘人员,我们公 司也不例外,从一个人力资源公司借用了一大批的低技术、低工资的人员,分配到各个子公 司,总共有将近200 号人,然后就找我们部门老大谈判,说要增加一个功能借用人员管理, 老大一看有钱赚呀,一拍大腿,做! 我带人过去一调研,不是这么简单,人力资源公司有一套自己的人员管理系统,我们公 您的设计模式 第 55 页 司需要把我们使用到的人员信息传输到我们的系统中,系统之间的传输使用RMI(Remote Method Invocation,远程对象调用)的方式,但是有一个问题人力资源公司的人员对象和 我们系统的对象不相同呀,他们的对象是这样的: 人员资源公司是把人的信息分为了三部分:基本信息,办公信息和个人家庭信息,并且 都放到了HashMap 中,比如人员的姓名放到BaseInfo 信息中,家庭地址放到HomeInfo 中, 这咱不好说他们系统设计的不好,那问题是咱的系统要和他们系统有交互,怎么办?使用适 配器模式,类图如下: 您的设计模式 第 56 页 大家可能会问,这两个对象都不在一个系统中,你如何使用呢?简单!RMI 已经帮我们 做了这件事情,只要有接口,就可以把远程的对象当成本地的对象使用,这个大家有时间可 以去看一下RMI 文档,不多说了。通过适配器,把OuterUser 伪装成我们系统中一个 IUserInfo 对象,这样,我们的系统基本不用修改什么程序员,所有的人员查询、调用跟本 地一样样的,说的口干舌燥,那下边我们来看具体的代码实现: 首先看IUserInfo.java 的代码: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 用户信息对象 */ public interface IUserInfo { //获得用户姓名 public String getUserName(); //获得家庭地址 public String getHomeAddress(); //手机号码,这个太重要,手机泛滥呀 public String getMobileNumber(); //办公电话,一般式座机 public String getOfficeTelNumber(); //这个人的职位是啥 public String getJobPosition(); //获得家庭电话,这个有点缺德,我是不喜欢打家庭电话讨论工作 public String getHomeTelNumber(); } 然后看这个接口的实现类: package com.cbf4life; 您的设计模式 第 57 页 /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. */ public class UserInfo implements IUserInfo { /* * 获得家庭地址,下属送礼也可以找到地方 */ public String getHomeAddress() { System.out.println('这里是员工的家庭地址....'); return null; } /* * 获得家庭电话号码 */ public String getHomeTelNumber() { System.out.println('员工的家庭电话是....'); return null; } /* * 员工的职位,是部门经理还是小兵 */ public String getJobPosition() { System.out.println('这个人的职位是BOSS....'); return null; } /* * 手机号码 */ public String getMobileNumber() { System.out.println('这个人的手机号码是0000....'); return null; } /* * 办公室电话,烦躁的时候最好“不小心”把电话线踢掉,我经常这么干,对己对人都 有好处 */ public String getOfficeTelNumber() { System.out.println('办公室电话是....'); 您的设计模式 第 58 页 return null; } /* * 姓名了,这个老重要了 */ public String getUserName() { System.out.println('姓名叫做...'); return null; } } 可能有人要问了,为什么要把电话号码、手机号码都设置成String 类型,而不是int 类型,大家觉的呢?题外话,这个绝对应该是String 类型,包括数据库也应该是varchar 类型的,手机号码有小灵通带区号的,比如02100001,这个你用数字怎么表示?有些人要 在手机号码前加上0086 后再保存,比如我们公司的印度阿三就是这样,喜欢在手机号码前 0086 保存下来,呵呵,我是想到啥就说啥,啰嗦了点。继续看我们的代码,下面看我们系 统的应用如何调用UserInfo 的信息: package com.cbf4life; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 这就是我们具体的应用了,比如老板要查所有的20-30的女性信息 */ public class App { public static void main(String[] args) { //没有与外系统连接的时候,是这样写的 IUserInfo youngGirl = new UserInfo(); //从数据库中查到101个 for(int i=0;i<101;i++){ youngGirl.getMobileNumber(); } } 您的设计模式 第 59 页 } 这老板,比较那个,为什么是101,是男生都应该知道吧,111 代表男生,101 代表女 生,呵呵,是不是比较色呀。从数据库中生成了101 个UserInfo 对象,直接打印出来就成 了。那然后增加了外系统的人员信息,怎么处理呢?下面是IOuterUser.java 的源代码: package com.cbf4life; import java.util.Map; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 外系统的人员信息 */ @SuppressWarnings('all') public interface IOuterUser { //基本信息,比如名称,性别,手机号码了等 public Map getUserBaseInfo(); //工作区域信息 public Map getUserOfficeInfo(); //用户的家庭信息 public Map getUserHomeInfo(); } 我们再看看外系统的用户信息的具体实现类: package com.cbf4life; import java.util.HashMap; import java.util.Map; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 外系统的用户信息的实现类 */ @SuppressWarnings('all') 您的设计模式 第 60 页 public class OuterUser implements IOuterUser { /* * 用户的基本信息 */ public Map getUserBaseInfo() { HashMap baseInfoMap = new HashMap(); baseInfoMap.put('userName', '这个员工叫混世魔王....'); baseInfoMap.put('mobileNumber', '这个员工电话是....'); return baseInfoMap; } /* * 员工的家庭信息 */ public Map getUserHomeInfo() { HashMap homeInfo = new HashMap(); homeInfo.put('homeTelNumbner', '员工的家庭电话是....'); homeInfo.put('homeAddress', '员工的家庭地址是....'); return homeInfo; } /* * 员工的工作信息,比如职位了等 */ public Map getUserOfficeInfo() { HashMap officeInfo = new HashMap(); officeInfo.put('jobPosition','这个人的职位是BOSS...'); officeInfo.put('officeTelNumber', '员工的办公电话是....'); return officeInfo; } } 那怎么把外系统的用户信息包装成我们公司的人员信息呢?看下面的OuterUserInfo 类源码,也就是我们的适配器: 您的设计模式 第 61 页 package com.cbf4life; import java.util.Map; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 把OuterUser包装成UserInfo */ @SuppressWarnings('all') public class OuterUserInfo extends OuterUser implements IUserInfo { private Map baseInfo = super.getUserBaseInfo(); //员工的基本信息 private Map homeInfo = super.getUserHomeInfo(); //员工的家庭信息 private Map officeInfo = super.getUserOfficeInfo(); //工作信息 /* * 家庭地址 */ public String getHomeAddress() { String homeAddress = (String)this.homeInfo.get('homeAddress'); System.out.println(homeAddress); return homeAddress; } /* * 家庭电话号码 */ public String getHomeTelNumber() { String homeTelNumber = (String)this.homeInfo.get('homeTelNumber'); System.out.println(homeTelNumber); return homeTelNumber; } /* *职位信息 */ public String getJobPosition() { String jobPosition = (String)this.officeInfo.get('jobPosition'); System.out.println(jobPosition); return jobPosition; 您的设计模式 第 62 页 } /* * 手机号码 */ public String getMobileNumber() { String mobileNumber = (String)this.baseInfo.get('mobileNumber'); System.out.println(mobileNumber); return mobileNumber; } /* * 办公电话 */ public String getOfficeTelNumber() { String officeTelNumber = (String)this.officeInfo.get('officeTelNumber'); |
|
来自: 昵称35402089 > 《设计模式》