大话设计模式

内容简介

本书出版后的读者评论

前言

第1章 代码无错就是优?——简单工厂模式

1.1 面试受挫

1.2 初学者代码毛病

1.3 代码规范

1.4 面向对象编程

1.5 活字印刷,面向对象

1.6 面向对象的好处

1.7 复制vs.复用

1.8 业务的封装

1.9 紧耦合vs.松耦合

1.10 简单工厂模式

1.11 UML类图

第2章 商场促销——策略模式

2.1 商场收银软件

2.2 增加打折

2.3 简单工厂实现

2.4 策略模式

2.5 策略模式实现

2.6 策略与简单工厂结合

2.7 策略模式解析

第3章 拍摄UFO——单一职责原则

3.1 新手机

3.2 拍摄

3.3 没用的东西

3.4 单一职责原则

3.5 方块游戏的设计

3.6 手机职责过多吗?

第4章 考研求职两不误——开放-封闭原则

4.1 考研失败

4.2 开放-封闭原则

4.3 何时应对变化

4.4 两手准备,并全力以赴

第5章 会修电脑不会修收音机?——依赖倒转原则

5.1 MM请求修电脑

5.2 电话遥控修电脑

5.3 依赖倒转原则

5.4 里氏代换原则

5.5 修收音机

第6章 穿什么有这么重要?——装饰模式

6.1 穿什么有这么重要?

6.2 小菜扮靓第一版

6.3 小菜扮靓第二版

6.4 装饰模式

6.5 小菜扮靓第三版

6.6 装饰模式总结

第7章 为别人做嫁衣——代理模式

7.1 为别人做嫁衣!

7.2 没有代理的代码

7.3 只有代理的代码

7.4 符合实际的代码

7.5 代理模式

7.6 代理模式应用

7.7 秀才让小六代其求婚

第8章 雷锋依然在人间——工厂方法模式

8.1 再现活雷锋

8.2 简单工厂模式实现

8.3 工厂方法模式实现

8.4 简单工厂vs.工厂方法

8.5 雷锋工厂

第9章 简历复印——原型模式

9.1 夸张的简历

9.2 简历代码初步实现

9.3 原型模式

9.4 简历的原型实现

9.5 浅复制与深复制

9.6 简历的深复制实现

9.7 复制简历vs.手写求职信

第10章 考题抄错会做也白搭——模板方法模式

10.1 选择题不会做,蒙呗!

10.2 重复=易错+难改

10.3 提炼代码

10.4 模板方法模式

10.5 模板方法模式特点

10.6 主观题,看你怎么蒙

第11章 无熟人难办事?——迪米特法则

11.1 第一天上班

11.2 无熟人难办事

11.3 迪米特法则

第12章 牛市股票还会亏钱?——外观模式

12.1 牛市股票还会亏钱?

12.2 股民炒股代码

12.3 投资基金代码

12.4 外观模式

12.5 何时使用外观模式

第13章 好菜每回味不同——建造者模式

13.1 炒面没放盐

13.2 建造小人一

13.3 建造小人二

13.4 建造者模式

13.5 建造者模式解析

13.6 建造者模式基本代码

第14章 老板回来,我不知道——观察者模式

14.1 老板回来?我不知道!

14.2 双向耦合的代码

14.3 解耦实践一

14.4 解耦实践二

14.5 观察者模式

14.6 观察者模式特点

14.7 观察者模式的不足

14.8 事件委托实现

14.9 事件委托说明

14.10 石守吉失手机后的委托

第15章 就不能不换DB吗?——抽象工厂模式

15.1 就不能不换DB吗?

15.2 最基本的数据访问程序

15.3 用了工厂方法模式的数据访问程序

15.4 用了抽象工厂模式的数据访问程序

15.5 抽象工厂模式

15.6 抽象工厂模式的优点与缺点

15.7 用简单工厂来改进抽象工厂

15.8 用反射+抽象工厂的数据访问程序

15.9 用反射+配置文件实现数据访问程序

15.10 无痴迷,不成功

第16章 无尽加班何时休——状态模式

16.1 加班,又是加班!

16.2 工作状态-函数版

16.3 工作状态-分类版

16.4 方法过长是坏味道

16.5 状态模式

16.6 状态模式好处与用处

16.7 工作状态-状态模式版

第17章 在NBA我需要翻译——适配器模式

17.1 在NBA我需要翻译!

17.2 适配器模式

17.3 何时使用适配器模式

17.4 篮球翻译适配器

17.5 适配器模式的.NET应用 17.6 扁鹊的医术 第18章 如果再回到从前——备忘录模式 18.1 如果再给我一次机会…… 18.2 游戏存进度 18.3 备忘录模式 18.4 备忘录模式基本代码 18.5 游戏进度备忘 第19章 分公司=一部门——组合模式 19.1 分公司不就是一部门吗? 19.2 组合模式 19.3 透明方式与安全方式 19.4 何时使用组合模式 19.5 公司管理系统 19.6 组合模式好处 第20章 想走?可以!先买票——迭代器模式 20.1 乘车买票,不管你是谁! 20.2 迭代器模式 20.3 迭代器实现 20.4 .NET的迭代器实现 20.5 迭代高手 第21章 有些类也需计划生育——单例模式 21.1 类也需要计划生育 21.2 判断对象是否是null 21.3 生还是不生是自己的责任 21.4 单例模式 21.5 多线程时的单例 21.6 双重锁定 21.7 静态初始化 第22章 手机软件何时统一——桥接模式 22.1 凭什么你的游戏我不能玩 22.2 紧耦合的程序演化 22.3 合成/聚合复用原则
22.4 松耦合的程序 22.5 桥接模式 22.6 桥接模式基本代码 22.7 我要开发“好”游戏 第23章 烤羊肉串引来的思考——命令模式 23.1 吃烤羊肉串! 23.2 烧烤摊vs.烧烤店 23.3 紧耦合设计 23.4 松耦合设计 23.5 松耦合后 23.6 命令模式 23.7 命令模式作用 第24章 加薪非要老总批?——职责链模式 24.1 老板,我要加薪! 24.2 加薪代码初步 24.3 职责链模式 24.4 职责链的好处 24.5 加薪代码重构 24.6 加薪成功 第25章 世界需要和平——中介者模式 25.1 世界需要和平! 25.2 中介者模式 25.3 安理会做中介 25.4 中介者模式优缺点 第26章 项目多也别傻做——享元模式 26.1 项目多也别傻做! 26.2 享元模式 26.3 网站共享代码 26.4 内部状态与外部状态 26.5 享元模式应用 第27章 其实你不懂老板的心——解释器模式 27.1 其实你不懂老板的心 27.2 解释器模式
27.3 解释器模式好处 27.4 音乐解释器 27.5 音乐解释器实现 27.6 料事如神 第28章 男人和女人——访问者模式 28.1 男人和女人! 28.2 最简单的编程实现 28.3 简单的面向对象实现 28.4 用了模式的实现 28.5 访问者模式 28.6 访问者模式基本代码 28.7 比上不足,比下有余 第29章 OOTV杯超级模式大赛——模式总结 29.1 演讲任务 29.2 报名参赛 29.3 超模大赛开幕式 29.4 创建型模式比赛 29.5 结构型模式比赛 29.6 行为型模式一组比赛 29.7 行为型模式二组比赛 29.8 决赛 29.9 梦醒时分 29.10 没有结束的结尾 附录A 培训实习生——面向对象基础 A.1 培训实习生 A.2 类与实例 A.3 构造方法 A.4 方法重载 A.5 属性与修饰符 A.6 封装 A.7 继承 A.8 多态 A.9 重构
A.10 抽象类 A.11 接口 A.12 集合 A.13 泛型 A.14 委托与事件 A.15 客套 附录B 参考文献

第1章 代码无错就是优?——简单工厂模式

1.1 面试受挫

小菜今年计算机专业大四了,学了不少软件开发方面的东西,也学着编了些小程序,

踌躇满志,一心要找一个好单位。当投递了无数份简历后,终于收到了一个单位的面试通

知,小菜欣喜若狂。

到了人家单位,前台小姐给了他一份题目,上面写着:“请用C++、Java、C#或

VB.NET任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符

号,得到结果。”

小菜一看,这个还不简单,三下五除二,10分钟不到,小菜写完了,感觉也没错误。

交卷后,单位说一周内等通知吧。于是小菜只得耐心等待。可是半个月过去了,什么消息

也没有,小菜很纳闷,我的代码实现了呀,为什么不给我机会呢。

时间: 2月26日20点 地点: 大鸟房间 人物: 小菜、大鸟

小菜找到从事软件开发工作七年的表哥大鸟,请教原因,大鸟问了题目和了解了小菜

代码的细节以后,哈哈大笑,说道:“小菜呀小菜,你上当了,人家单位出题的意思,你

完全都没明白,当然不会再联系你了。”

小菜说:“我的代码有错吗?单位题目不就是要我实现一个计算器的代码吗,我这样

写有什么问题。”

class Program {
static void Main(string[] args) { Console.Write(“请输入数字A:”); string A = Console.ReadLine(); Console.Write(“请选择运算符号(+、-、*、/):”); string B = Console.ReadLine(); Console.Write(“请输入数字B:”); string C = Console.ReadLine(); string D = “”; if(B == “+”) D = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(C)); if(B == “-”) D = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(C)); if(B == “*”) D = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(C)); if(B == “/”) D = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(C)); Console.WriteLine(“结果是:” + D); } }

1.2 初学者代码毛病

大鸟说:“且先不说出题人的意思,单就你现在的代码,就有很多不足的地方需要改

进。”

1.3 代码规范

“哦,说得没错,这个我以前听老师说过,可是从来没有在意过,我马上改,改完再

给你看看。”

class Program { static void Main(string[] args) { try { Console.Write(“请输入数字A:”); string strNumberA = Console.ReadLine(); Console.Write(“请选择运算符号(+、-、*、/):”); string strOperate = Console.ReadLine(); Console.Write(“请输入数字B:”); string strNumberB = Console.ReadLine(); string strResult = “”; switch(strOperate) { case “+”: strResult = Convert.ToString(Convert.ToDouble(strNumberA) + Convert.ToDouble(strNumberB)); break; case “-”: strResult = Convert.ToString(Convert.ToDouble(strNumberA) - Convert.ToDouble(strNumberB)); break; case “*”: strResult = Convert.ToString(Convert.ToDouble(strNumberA) * Convert.ToDouble(strNumberB));
break; case “/”: if(strNumberB != “0”) strResult = Convert.ToString(Convert.ToDouble(strNumber / Convert.ToDouble(strNumberB)); else strResult = “除数不能为0”; break; } Console.WriteLine(“结果是:” + strResult); Console.ReadLine(); } catch(Exception ex) { Console.WriteLine(“您的输入有错:” + ex.Message); } } }

大鸟:“吼吼,不错,不错,改得很快嘛?至少就目前代码来说,实现计算器是没有

问题了,但这样写出的代码是否合出题人的意思呢?”

小菜:“你的意思是面向对象?”

大鸟:“哈,小菜非小菜也!”

1.4 面向对象编程

小菜:“我明白了,他说用任意一种面向对象语言实现,那意思就是要用面向对象的

编程方法去实现,对吗?OK,这个我学过,只不过当时我没想到而已。”

大鸟:“所有编程初学者都会有这样的问题,就是碰到问题就直觉地用计算机能够理

解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思

考,比如计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如

何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的

需求,程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。”

小菜:“鸟哥呀,我有点糊涂了,如何才能容易维护,容易扩展,又容易复用呢,能

不能具体点?”

1.5 活字印刷,面向对象

大鸟:“这样吧,我给你讲个故事。你就明白了。”

“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一

片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大

发,不觉吟道:‘喝酒唱歌,人生真爽。……’。众文武齐呼:‘丞相好诗!’于是一臣子速

命印刷工匠刻版印刷,以便流传天下。”

“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当

歌’较好!’,于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦

不迭。只得照办。”

“样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽太过直

接,应改问语才够意境,因此应改为‘对酒当歌,人生几何?……’当臣转告工匠之时,工

匠晕倒……!”

“小菜你说,这里面问题出在哪里?”大鸟问道。

小菜说:“是不是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整

个刻板全部重新刻。”

大鸟:“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白

做。岂不妙哉。”

“第一,要改,只需更改要改之字,此为可维护 ;第二,这些字并非用完这次就无

用,完全可以在后来的印刷中重复使用,此乃可复用 ;第三,此诗若要加字,只需另刻

字加入即可,这是可扩展 ;第四,字的排列其实可能是竖排可能是横排,此时只需将活

字移动就可做到满足排列需求,此是灵活性好 。”

“而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加

字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。”

小菜:“是的,小时候,我一直奇怪,为何火药、指南针、造纸术都是从无到有,从

未知到发现的伟大发明,而活字印刷仅仅是从刻版印刷到活字印刷的一次技术上的进步,

为何不是评印刷术为四大发明之一呢?原来活字印刷的成功是这个原因。”

1.6 面向对象的好处

大鸟:“哈,这下你明白了?我以前也不懂,不过做了软件开发几年后,经历了太多

的类似曹操这样的客户要改变需求,更改最初想法的事件,才逐渐明白当中的道理。其实

客观地说,客户的要求也并不过份,不就是改几个字吗,但面对已完成的程序代码,却是

需要几乎重头来过的尴尬,这实在是痛苦不堪。说白了,原因就是因为我们原先所写的程

序,不容易维护,灵活性差,不容易扩展,更谈不上复用,因此面对需求变化,加班加

点,对程序动大手术的那种无奈也就成了非常正常的事了。之后当我学习了面向对象的分

析设计编程思想,开始考虑通过封装、继承、多态把程序的耦合度降低 ,传统印刷术的

问题就在于所有的字都刻在同一版面上造成耦合度太高所致,开始用设计模式使得程序

更加的灵活,容易修改,并且易于复用。 体会到面向对象带来的好处,那种感觉应该就

如同是一中国酒鬼第一次喝到了茅台,西洋酒鬼第一次喝到了XO一样,怎个爽字可形容

呀。”

“是呀是呀,你说得没错,中国古代的四大发明,另三种应该都是科技的进步,伟大

的创造或发现。而唯有活字印刷,实在是思想的成功,面向对象的胜利。”小菜也兴奋起

来:“你的意思是,面试公司出题的目的是要我写出容易维护,容易扩展,又容易复用的

计算器程序?那该如何做呀?”

1.7 复制**vs.**复用

大鸟:“比如说,我现在要求你再写一个Windows的计算器,你现在的代码能不能复

用呢?”

小菜:“那还不简单,把代码复制过去不就行了吗?改动又不大,不算麻烦。”

大鸟:“小菜看来还是小菜呀,有人说初级程序员的工作就是Ctrl+C和Ctrl+V,这其

实是非常不好的编码习惯,因为当你的代码中重复的代码多到一定程度,维护的时候,可

能就是一场灾难。越大的系统,这种方式带来的问题越严重,编程有一原则,就是用尽可

能的办法去避免重复。想想看,你写的这段代码,有哪些是和控制台无关的,而只是和计

算器有关的?”

小菜:“你的意思是分一个类出来?哦,对的,让计算和显示分开。”

1.8 业务的封装

大鸟:“准确地说,就是让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。只

有分离开,才可以达到容易维护或扩展。”

小菜:“让我来试试看。”

Operation运算类

public class Operation { public static double GetResult(double numberA,double numberB,string operate) { double result = 0d; switch(operate) { case “+”: result = numberA + numberB; break; case “-”: result = numberA - numberB; break; case “*”: result = numberA * numberB; break; case “/”: result = numberA / numberB; break; } return result; } }

客户端代码

static void Main(string[] args) { try { Console.Write(“请输入数字A:”); string strNumberA = Console.ReadLine(); Console.Write(“请选择运算符号(+、-、*、/):”); string strOperate = Console.ReadLine(); Console.Write(“请输入数字B:”); string strNumberB = Console.ReadLine(); string strResult = “”; Convert.ToDouble(strNumberB),strOperate)); Console.WriteLine(“结果是:” + strResult); Console.ReadLine(); } catch(Exception ex) { Console.WriteLine(“您的输入有错:” + ex.Message); } }

strResult = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumberA),

小菜:“鸟哥,我写好了,你看看!”

大鸟:“孺鸟可教也,写得不错,这样就完全把业务和界面分离了。”

小菜心中暗骂:“你才是鸟呢。”口中说道:“如果你现在要我写一个Windows应用程

序的计算器,我就可以复用这个运算类(Operation)了。”

大鸟:“不单是Windows程序,Web版程序需要运算可以用它,PDA、手机等需要移

动系统的软件需要运算也可以用它呀。”

小菜:“哈,面向对象不过如此。下回写类似代码不怕了。”

大鸟:“别急,仅此而已,实在谈不上完全面向对象,你只用了面向对象三大特性中

的一个,还有两个没用呢?”

小菜:“面向对象三大特性不就是封装、继承和多态吗,这里我用到的应该是封装。

这还不够吗?我实在看不出,这么小的程序如何用到继承。至于多态,其实我一直也不太

了解它到底有什么好处,如何使用它。”

大鸟:“慢慢来,要学的东西多着呢,你好好想想该如何应用面向对象的继承和多

态。”

1.9 紧耦合**vs.**松耦合

第二天。

小菜问道:“你说计算器这样的小程序还可以用到面向对象三大特性?继承和多态怎

么可能用得上,我实在不能理解。”

大鸟:“小菜很有钻研精神嘛,好,今天我让你功力加深一级。你先要考虑一下,你

昨天写的这个代码,能否做到很灵活的可修改和扩展呢?”

小菜:“我已经把业务和界面分离了呀,这不是很灵活了吗?”

大鸟:“那我问你,现在如果我希望增加一个开根(sqrt)运算,你如何改?”

小菜:“那只需要改Operation类就行了,在switch中加一个分支就行了。”

大鸟:“问题是你要加一个平方根运算,却需要让加减乘除的运算都得来参与编译,

如果你一不小心,把加法运算改成了减法,这岂不是大大的糟糕。打个比方,如果现在公

司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪),市场销售人员(底

薪+提成),经理(年薪+股份)三种运算算法,现在要增加兼职工作人员(时薪)的算

法,但按照你昨天的程序写法,公司就必须要把包含原三种算法的运算类给你,让你修

改,你如果心中小算盘一打,‘TMD,公司给我的工资这么低,我真是郁闷,这下有机会

了’,于是你除了增加了兼职算法以外,在技术人员(月薪)算法中写了一句

if(员工是小菜) { salary = salary * 1.1; }

那就意味着,你的月薪每月都会增加10%(小心被抓去坐牢),本来是让你加一个功

能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。你明白了吗?”

小菜:“哦,你的意思是,我应该把加减乘除等运算分离,修改其中一个不影响另外

的几个,增加运算算法也不影响其他代码,是这样吗?”

大鸟:“自己想去吧,如何用继承和多态,你应该有感觉了。”

小菜:“OK,我马上去写。”

Operation运算类

public class Operation { private double _numberA = 0; private double _numberB = 0; public double NumberA { get { return _numberA; } set { _numberA = value; } } public double NumberB { get { return _numberB; } set { _numberB = value; } } public virtual double GetResult() { double result = 0; return result; } }

加减乘除类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DngihhlX-1691736445205)(https://gitee.com/Jschrodinger/typora-image/raw/master/b94f049b5c85e42d85f6f0d40a270544.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nXORTJaP-1691736445206)(https://gitee.com/Jschrodinger/typora-image/raw/master/8c10aefa20afdf3fab4bf68c3c0b36bd.png)]

小菜:“大鸟哥,我按照你说的方法写出来了一部分,首先是一个运算类,它有两个

Number属性,主要用于计算器的前后数,然后有一个虚方法GetResult(),用于得到结

果,然后我把加减乘除都写成了运算类的子类,继承它后,重写了GetResult()方法,这

样如果要修改任何一个算法,就不需要提供其他算法的代码了。但问题来了,我如何让计

算器知道我是希望用哪一个算法呢?”

(作者注:以上代码读者如果感觉阅读吃力,说明您对继承、多态、虚方法、方法重

写等概念的理解尚不够,建议先阅读本书的附录一,理解了这些基本概念后再继续往下阅

读。)

1.10 简单工厂模式

大鸟:“写得很不错嘛,大大超出我的想象了,你现在的问题其实就是如何去实例化

对象的问题,哈,今天心情不错,再教你一招‘简单工厂模式’,也就是说,到底要实例化

谁,将来会不会增加实例化的对象,比如增加开根运算,这是很容易变化的地方,应该考

虑用一个单独的类来做这个创造实例的过程,这就是工厂,来,我们看看这个类如何

写。”

简单运算工厂类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezSm6fD1-1691736445206)(https://gitee.com/Jschrodinger/typora-image/raw/master/1e6251f13dde7861a4425d6c8197309d.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8PaloqUX-1691736445206)(https://gitee.com/Jschrodinger/typora-image/raw/master/46301ebf4228c343dbdc35ae5803cf7a.png)]

大鸟:“哈,看到了吧,这样子,你只需要输入运算符号,工厂就实例化出合适的对

象,通过多态,返回父类的方式实现了计算器的结果。”

客户端代码

Operation oper; oper = OperationFactory.createOperate(“+”); oper.NumberA = 1; oper.NumberB = 2; double result = oper.GetResult();

大鸟:“哈,界面的实现就是这样的代码,不管你是控制台程序,Windows程序,

Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,如果有一天我们需

要更改加法运算,我们只需要改哪里?”

小菜:“改OperationAdd就可以了。”

大鸟:“那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余

弦等,如何做?”

小菜:“只要增加相应的运算子类就可以了呀。”

大鸟:“嗯?够了吗?”

小菜:“对了,还需要去修改运算类工厂,在switch中增加分支。”

大鸟:“哈,那才对,那如果要修改界面呢?”

小菜:“那就去改界面呀,关运算什么事呀。”

大鸟:“好了,最后,我们来看看这几个类的结构图。”

1.11 UML类图

小菜:“对了,我时常在一些技术书中看到这些类图表示,简单的还看得懂,有些标

记我很容易混淆。要不你给我讲讲吧。”

大鸟:“这个其实多看多用就熟悉了。我给你举一个例子,来看这样一幅图,其中就

包括了UML类图中的基本图示法。”

UML类图图示样例

大鸟:“首先你看那个‘动物’矩形框,它就代表一个类(Class)。类图分三层,第一

层显示类的名称,如果是抽象类,则就用斜体显示。第二层是类的特性,通常就是字段和

属性。第三层是类的操作,通常是方法或行为。注意前面的符号,‘+’表示public,‘-’表示

private,‘#’表示protected。”

大鸟:“然后注意左下角的‘飞翔’,它表示一个接口图,与类图的区别主要是顶端有

<<interface>>显示。第一行是接口名称,第二行是接口方法。接口还有另一种表示方法,

俗称棒棒糖表示法,比如图中的唐老鸭类就是实现了‘讲人话’的接口。”

小菜:“为什么要是‘讲人话’?”

大鸟:“鸭子本来也有语言,只不过只有唐老鸭是能讲人话的鸭子。”

小菜:“有道理。”

大鸟:“接下来就可讲类与类,类与接口之间的关系了。你可首先注意动物、鸟、

鸭、唐老鸭之间关系符号。”

小菜:“明白了,它们都是继承的关系,继承关系用空心三角形+实线来表示。”

大鸟:“我举的几种鸟中,大雁是最能飞的,我让它实现了飞翔接口。实现接口用空

心三角形+虚线来表示。”

大鸟:“你看企鹅和气候两个类,企鹅是很特别的鸟,会游不会飞。更重要的是,它

与气候有很大的关联。我们不去讨论为什么北极没有企鹅,为什么它们要每年长途跋涉。

总之,企鹅需要‘知道’气候的变化,需要‘了解’气候规律。当一个类‘知道’另一个类时,可

以用关联(association)。关联关系用实线箭头来表示。”

大鸟:“我们再来看大雁与雁群这两个类,大雁是群居动物,每只大雁都是属于一个

雁群,一个雁群可以有多只大雁。所以它们之间就满足聚合(Aggregation)关系。聚合

表示一种弱的**‘拥有关系,体现的是A对象可以包含B对象,但B对象不是A**对象的一部

分 [DPE](DPE表示此句摘自《设计模式》(第2版),详细摘要说明见附录二)。聚合

关系用空心的菱形+实线箭头来表示。”

大鸟:“合成(Composition,也有翻译成**‘组合的)是一种强的拥有’**关系,体现了

严格的部分和整体的关系,部分和整体的生命周期一样 [DPE]。在这里鸟和其翅膀就是

合成(组合)关系,因为它们是部分和整体的关系,并且翅膀和鸟的生命周期是相同的。

合成关系用实心的菱形+实线箭头来表示。另外,你会注意到合成关系的连线两端还有一

个数字‘1’和数字‘2’,这被称为基数。表明这一端的类可以有几个实例,很显然,一个鸟

应该有两只翅膀。如果一个类可能有无数个实例,则就用‘n’来表示。关联关系、聚合关

系也可以有基数的。”

大鸟:“动物几大特征,比如有新陈代谢,能繁殖。而动物要有生命力,需要氧气、

水以及食物等。也就是说,动物依赖于氧气和水。他们之间是依赖关系(Dependency),

用虚线箭头来表示。”

abstract class Animal { public Metabolism(Oxygen oxygen,Water water) { } }

小菜:“啊,看来UML类图也不算难呀。回想那天我面试题写的代码,我终于明白我

为什么写得不成功了,原来一个小小的计算器也可以写出这么精彩的代码,谢谢大鸟。”

大鸟:“吼吼,记住哦,编程是一门技术,更加是一门艺术 ,不能只满足于写完代码

运行结果正确就完事,时常考虑如何让代码更加简练,更加容易维护,容易扩展和复用,

只有这样才可以真正得到提高。写出优雅的代码真的是一种很爽的事情。UML类图也不

是一学就会的,需要有一个慢慢熟练的过程。所谓学无止境,其实这才是理解面向对象的

开始呢。”

第2章 商场促销——策略模式

2.1 商场收银软件

时间: 2月27日22点 地点: 大鸟房间 人物: 小菜、大鸟

“小菜,给你出个作业,做一个商场收银软件,营业员根据客户所购买商品的单价和

数量,向客户收费。”

“就这个?没问题呀。”小菜说,“用两个文本框来输入单价和数量,一个确定按钮来

算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,对,还需要

一个重置按钮来重新开始,不就行了?!”

商场收银系统v1.0关键代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXlAFVMv-1691736445207)(https://gitee.com/Jschrodinger/typora-image/raw/master/fa9e1a713a4771ca011ec39bafa90fdb.png)]

“大鸟,”小菜叫道,“来看看,这不就是你要的收银软件吗?我不到半小时就搞定

了。”

“哈哈,很快嘛,”大鸟说着,看了看小菜的代码。接着说:“现在我要求商场对商品

搞活动,所有的商品打八折。”

“那不就是在totalPrices后面乘以一个0.8吗?”

“小子,难道商场活动结束,不打折了,你还要再改一遍程序代码,然后再用改后的

程序去把所有机器全部安装一次吗?再说,还有可能因为周年庆,打五折的情况,你怎么

办?”

小菜不好意思道:“啊,我想得是简单了点。其实只要加一个下拉选择框就可以解决

你说的问题。”

大鸟微笑不语。

2.2 增加打折

商场收银系统v1.1关键代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZ2ngpnz-1691736445208)(https://gitee.com/Jschrodinger/typora-image/raw/master/0a8ee6241c6188a3d037b0e2f9320246.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nr6nC7RQ-1691736445208)(https://gitee.com/Jschrodinger/typora-image/raw/master/4c68d628070a7f28440725a717b9392f.png)]

“这下可以了吧,只要我事先把商场可能的打折都做成下拉选择框的项,要变化的可

能性就小多了。”小菜说道。

“这比刚才灵活性上是好多了,不过重复代码很多,像Convert.ToDouble(),你这里

就写了8遍,而且4个分支要执行的语句除了打折多少以外几乎没什么不同,应该考虑重构

一下。不过这还不是最主要的,现在我的需求又来了,商场的活动加大,需要有满300返

100的促销算法,你说怎么办?”

“满300返100,那要是700就要返200了?这个必须要写函数了吧?”

“小菜呀,看来之前教你的白教了,这里面看不出什么名堂吗?”

“哦!我想起来了,你的意思是简单工厂模式是吧,对的对的,我可以先写一个父

类,再继承它实现多个打折和返利的子类,利用多态,完成这个代码。”

“你打算写几个子类?”

“根据需求呀,比如八折、七折、五折、满300送100、满200送50……要几个写几

个。”

“小菜又不动脑子了,有必要这样吗?如果我现在要三折,我要满300送80,你难道再

去加子类?你不想想看,这当中哪些是相同的,哪些是不同的?”

2.3 简单工厂实现

“对的,这里打折基本都是一样的,只要有个初始化参数就可以了。满几送几的,需

要两个参数才行,明白,现在看来不麻烦了。”

**“**面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽

象,具有相同属性和功能的对象的抽象集合才是类。 打一折和打九折只是形式的不同,

抽象分析出来,所有的打折算法都是一样的,所以打折算法应该是一个类。好了,空话已

说了太多,写出来才是真的懂。”

大约1个小时后,小菜交出了第三份的作业。

代码结构图

现金收费抽象类

正常收费子类

打折收费子类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETSnfmgA-1691736445208)(https://gitee.com/Jschrodinger/typora-image/raw/master/e54f22665d7fcae796bb9da9d1b85f42.png)]

返利收费子类

现金收费工厂类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnZlgJQ4-1691736445209)(https://gitee.com/Jschrodinger/typora-image/raw/master/cc84dfadbceaa5148f025c188c8e3122.png)]

客户端程序主要部分

“大鸟,搞定,这次无论你要怎么改,我都可以简单处理就行了。”小菜自信满满地

说。

“是吗,我要是需要打五折和满500送200的促销活动,如何办?”

“只要在现金工厂当中加两个条件,在界面的下拉选项框里加两项,就OK了。”

“现金工厂?!你当是生产钞票呀。是收费对象生成工厂才准确。说得不错,如果我

现在需要增加一种商场促销手段,满100积分10点,以后积分到一定时候可以领取奖品如

何做?”

“有了工厂,何难?加一个积分算法,构造方法有两个参数:条件和返点,让它继承

CashSuper,再到现金工厂,哦,不对,是收费对象生成工厂里增加满100积分10点的分支

条件,再到界面稍加改动,就行了。”

“嗯,不错。你对简单工厂用得很熟练了嘛。”大鸟接着说:“简单工厂模式虽然也能

解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收

费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改

动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式,所以用它不是最好

的办法。面对算法的时常变动,应该有更好的办法。好好去研究一下其他的设计模式,你

会找到答案的。”

小菜进入了沉思中……

2.4 策略模式

时间: 2月28日19点 地点: 大鸟房间 人物: 小菜、大鸟

小菜次日来找大鸟,说:“我找到相关的设计模式了,应该是策略模式(Strategy)。

策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变

化,不会影响到使用算法的客户。看来商场收银系统应该考虑用策略模式?”

“你问我?你说呢?”大鸟笑道,“商场收银时如何促销,用打折还是返利,其实都是

一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这

些算法是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很

重要的思维方式。我们来看看策略模式的结构图和基本代码。”

策略模式(Strategy)结构图

Strategy类,定义所有支持的算法的公共接口

//抽象算法类 abstract class Strategy { //算法方法 public abstract void AlgorithmInterface(); }

ConcreteStrategy,封装了具体的算法或行为,继承于Strategy

//具体算法A class ConcreteStrategyA : Strategy { //算法A实现方法 public override void AlgorithmInterface() { Console.WriteLine(“算法A实现”); } } //具体算法B class ConcreteStrategyB : Strategy { //算法B实现方法 public override void AlgorithmInterface() { Console.WriteLine(“算法B实现”); } } //具体算法C class ConcreteStrategyC : Strategy { //算法C实现方法
public override void AlgorithmInterface() { Console.WriteLine(“算法C实现”); } }

Context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。

客户端代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oJ39FVZR-1691736445209)(https://gitee.com/Jschrodinger/typora-image/raw/master/e9f8d957e4dfd9b4744f0e6c7ccba620.png)]

2.5 策略模式实现

“我明白了,”小菜说,“我昨天写的CashSuper就是抽象策略,而正常收费

CashNormal、打折收费CashRebate和返利收费CashReturn就是三个具体策略,也就是策略

模式中说的具体算法,对吧?”

“是的,来吧,你模仿策略模式的基本代码,改写一下你的程序。”

“其实不麻烦,原来写的CashSuper、CashNormal、CashRebate和CashReturn都不用更

改了,只要加一个CashContext类,并改写一下客户端就行了。”

商场收银系统v1.2

代码结构图

CashContext类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGScSzA9-1691736445209)(https://gitee.com/Jschrodinger/typora-image/raw/master/3725feecf781e81a4b69b076cdafad23.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkLoHYTN-1691736445210)(https://gitee.com/Jschrodinger/typora-image/raw/master/4dd134a8a17bb357d72b5371e93371f2.png)]

客户端主要代码

“大鸟,代码是模仿着写出来了。但我感觉这样子做不又回到了原来的老路了吗,在

客户端去判断用哪一个算法?”

“是的,但是你有没有什么好办法,把这个判断的过程从客户端程序转移走呢?”

“转移?不明白,原来我用简单工厂是可以转移的,现在这样子如何做到?”

“难道简单工厂就一定要是一个单独的类吗?难道不可以与策略模式的Context结

合?”

“哦,我明白你的意思了。我试试看。”

2.6 策略与简单工厂结合

改造后的CashContext

客户端代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xts66Xpb-1691736445210)(https://gitee.com/Jschrodinger/typora-image/raw/master/d15a067de07895c949501f3a293425e9.png)]

“嗯,原来简单工厂模式并非只有建一个工厂类的做法,还可以这样子做。此时比刚

才的模仿策略模式的写法要清楚多了,客户端代码简单明了。”

“那和你写的简单工厂的客户端代码比呢?观察一下,找出它们的不同之处。”

//简单工厂模式的用法 CashSuper csuper = CashFactory.createCashAccept(cbxType.SelectedItem.ToString( …=csuper.GetResult(…)
//策略模式与简单工厂结合的用法 CashContext csuper =new CashContext(cbxType.SelectedItem.ToString()); …=csuper.GetResult(…);

“你的意思是说,简单工厂模式我需要让客户端认识两个类,CashSuper和

CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类

CashContext就可以了。耦合更加降低。”

“说得没错,我们在客户端实例化的是CashContext的对象,调用的是CashContext的方

法GetResult,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不

让客户端认识了。”

2.7 策略模式解析

“回过头来反思一下策略模式,策略模式是一种定义一系列算法的方法,从概念上来

看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所

有的算法,减少了各种算法类与使用算法类之间的耦合**[DPE]** 。”大鸟总结道。

“策略模式还有些什么优点?”小菜问道。

策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继

承有助于析取出这些算法中的公共功能**[DP]** 。对于打折、返利或者其他的算法,其实都

是对实际商品收费的一种计算方式,通过继承,可以得到它们的公共功能,你说这公共功

能指什么?”

“公共的功能就是获得计算费用的结果GetResult,这使得算法间有了抽象的父类

CashSuper。”

“对,很好。另外一个策略模式的优点是简化了单元测试,因为每个算法都有自己的

类,可以通过自己的接口单独测试**[DPE]** 。”

“每个算法可保证它没有错误,修改其中任一个时也不会影响其他的算法。这真的是

非常好。”

“哈,小菜今天表现不错,我所想的你都想到了。”大鸟表扬了小菜,“还有,在最开

始编程时,你不得不在客户端的代码中为了判断用哪一个算法计算而用了switch条件分

支,这也是正常的。因为,当不同的行为堆砌在一个类中时,就很难避免使用条件语句

来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行

为的类中消除条件语句**[DP]** 。就商场收银系统的例子而言,在客户端的代码中就消除条

件语句,避免了大量的判断。这是非常重要的进展。你能用一句话来概况这个优点

吗?”大鸟总结后问道。

“策略模式封装了变化。”小菜快速而坚定的说。

“说得非常好,策略模式就是用来封装算法的,但在实践中,我们发现可以用它来

封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规

则,就可以考虑使用策略模式处理这种变化的可能性**[DPE]** ”。

“但我感觉在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并

转给策略模式的Context对象**[DPE]** 。这本身并没有解除客户端需要选择判断的压力,而

策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最

大化地减轻了客户端的职责。”

“是的,这已经比起初的策略模式好用了,不过,它依然不够完美。”

“哦,还有什么不足吗?”

“因为在CashContext里还是用到了switch,也就是说,如果我们需要增加一种算法,

比如‘满200送50’,你就必须要更改CashContext中的switch代码,这总还是让人很不爽

呀。”

“那你说怎么办,有需求就得改呀,任何需求的变更都是需要成本的 。”

“但是成本的高低还是有差异的。高手和菜鸟的区别就是高手可以花同样的代价获得

最大的收益或者说做同样的事花最小的代价。面对同样的需求,当然是改动越小越好。”

“你的意思是说,还有更好的办法?”

“当然。这个办法就是用到了反射技术,不是常有人讲,‘反射反射,程序员的快

乐’,不过今天就不讲了,以后会再提它的。”

“反射真有这么神奇?”小菜疑惑地望向了远方。

(注:在抽象工厂模式章节有对反射的讲解)

第3章 拍摄UFO——单一职责原则

3.1 新手机

时间: 2月28日18点 地点: 小菜大鸟居住的小区附近 人物: 小菜、大鸟

大鸟小菜晚上晚饭过后,在外面散步。

大鸟:“小菜,刚换的手机感觉如何?”

小菜:“哈,当然是怎个爽字了得,可以听音乐、玩游戏、拍照、摄像,功能全着

呢!”

大鸟:“你们这些小年轻,只会赶时髦,手机要那么多功能干吗?能打电话就可以

了。”

小菜:“这你就不懂了吧,比如你出门旅游,数码相机一定要的吧,拍照是最起码的

旅游需求;有摄像机会更好,动的影像不是更有保留价值吗;一路上无聊的时候,打打游

戏总是需要的,游戏机要准备;坐在大巴士上,看着窗外美景,听听音乐应该也属于正常

需求吧,MP3一定要带着了;有时或许还需要什么GPS来定定位,上网看看新闻,发发邮

件,查查股票行情,这些需求如何办,总不能带着笔记本电脑在路上跑吧。这些东西且不

说本身就很重,很麻烦,就说这些东西的充电器,就是五花八门,估计单就带这些东西,

你就得累个半死了。”

大鸟:“你说得也没错,现在电子产品能玩的东西太多……”

小菜:“啊,大鸟!快看!”

3.2 拍摄

小菜惊呼,左手拉住大鸟的手,右手指向了天空。

大鸟跟着抬头一看,“那应该是架飞机吧!”

“不可能,”小菜坚决地说,“飞机哪有没翅膀的,那个东西飞得很奇怪,你看,你

看,它停在空中,普通飞机怎么会在空中停下来。”

“是不太像飞机,飞碟?!——傻菜,快点用你手机录像呀!”

“是是是,啊,这手机怎么……等等,”小菜手忙脚乱。

“看你慌得,”大鸟说,“快些,马上可能就没了。”

“好了好了,”小菜终于打开了手机的摄像功能,对准了天空,“主要是对新功能不熟

悉,你看,这家伙飞得多快。”

“嗯,它应该是飞碟,不然不可能这种样子的,以前也没有听说过这玩意,”大鸟肯定

道,“还好你这手机可以摄像——啊,它飞跑了,你拍下来了没有?”

“好了,我都拍下来了,有点不太清楚,回去放电脑上看看吧。”小菜很开心,他的新

手机发挥大作用了,“头一次看到UFO,就拍到了,这下可是大新闻了。”

“是呀,我也头一次看到,我们太幸运了。”

3.3 没用的东西

回到家中。小菜将手机文件传入电脑。

“这什么呀,黑乎乎的,什么也看不清。”大鸟大为失望。

“那不是有一个小白点吗?”小菜想极力申辩。

“那白点就和液晶显示器里的坏点一样,这如何看得出是UFO呢,说给别人谁信呀。”

“嗨!是的。”小菜也承认了这个事实,“这手机拍出来的东西没办法看呀,根本算不

上是UFO的证据。”

小菜拿起手机,一脸苦相,对着它说道:“狗屁,要你这么多功能有鸟用,关键时刻

就萎掉,我砸……”小菜举起手机欲往地上砸去。

3.4 单一职责原则

“砸呀,你砸呀!”大鸟笑嘻嘻地看着小菜,“哼哼,我就知道你舍不得,不过你的手

机的确是太没用,这么好的机遇,都没有录成,如果是摄像机,效果一定不会差,因为当

时我们眼睛看得很清楚呀。这下说给谁,谁也不信呀!大多数时候,一件产品简单一些,

职责单一一些,或许是更好的选择。这就和设计模式中的一大原则——单一职责的道理是

一样的。”

“哦,听字面意思,单一职责原则,意思就是说,功能要单一?”

“哈,可以简单地这么理解,它的准确解释是,就一个类而言,应该仅有一个引起它

变化的原因**[ASD]** 。我们在做编程的时候,很自然地就会给一个类加各种各样的功能,

比如我们写一个窗体应用程序,一般都会生成一个Form1这样的类,于是我们就把各种各

样的代码,像某种商业运算的算法呀,像数据库访问的SQL语句呀什么的都写到这样的类

当中,这就意味着,无论任何需求要来,你都需要更改这个窗体类,这其实是很糟糕的,

维护麻烦,复用不可能,也缺乏灵活性。”

“是的,我写代码一般刚开始就是把所有的方法直接写在窗体类的代码当中的。”

3.5 方块游戏的设计

“我们再来举些例子,比如就拿手机里的俄罗斯方块游戏为例。要是让你开发这个小

游戏,你如何考虑?”大鸟问道。

“我想想,首先它方块下落动画的原理是画四个小方块,擦掉,然后再在下一行画四

个方块。不断地绘出和擦掉就形成了动画,所以应该要有画和擦方块的代码。然后左右键

实现左移和右移,下键实现加速,上键实现旋转,这其实都应该是函数,当然左右移动需

要考虑碰撞的问题,下移需要考虑堆积和消层的问题。”

“OK,你也说了不少了。如果就用WinForm的方式开发,你打算怎么开发呢?”

“那当然是先建立一个窗体Form,然后加一个用于游戏框的控件,比如Panel或者

PictureBox,一个按钮Button来控制‘开始’,最后再放一个Timer控件用于分时动画的编

程。写代码当然就是编写Timer_Tick事件来绘出和擦除方块,并做出堆积和消层的判断。

再编写控件的键盘事件,按了左箭头则左移,右箭头则右移等等。对了,还需要用到些

GDI+技术的方法来画方块和擦方块。”

“你能不能就这些代码划分一下类呢?”

“分类?这里好像关键在于各种事件代码如何写吧,这里有什么类可言呢?”

“看来你的面向过程开发已经根深蒂固了。你把所有的代码都写在了Form1.cs这个类

里,你觉得这合理吗?”

“可能不合理,但我实在没想出怎么分离它。”

“打个比方,如果现在要你写的是手机版的俄罗斯方块程序,即Pocket PC或者

Windows CE上运行的程序,它们可以安装.NET框架的精简版,运行C#语言编写的应用程

序,但PC上的普通WinForm界面的程序不能使用。那你现在这个代码有什么可以复用的

吗?”

“你都已经说了,不能使用,我当然就没法使用了。Copy过去,再针对代码做些改进

吧。”

“但这当中,有些东西是始终没变的。”

“你是说,下落、旋转、碰撞判断、移动、堆积这些游戏逻辑吧?”

“说得没错,这些都是和游戏有关的逻辑,和界面如何表示没有什么关系,为什么要

写在一个类里面呢?如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个

职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设

计,当变化发生时,设计会遭受到意想不到的破坏**[ASD]** 。事实上,你完全可以找出哪

些是界面,哪些是游戏逻辑,然后进行分离。”

“但我还是不明白,如何分离开。”

“你仔细想想看,方块的可移动的游戏区域,可以设计为一个二维整型数组用来表示

坐标,宽10,高20,比如‘int[,] arraySquare=new int[10,20];’,那么整个方块的移动其实

就是数组的下标变化,比如原方块在arraySquare [3,5]上,则下移时变成arraySquare [3,

6],如果下移同时还按了左键,则是arraySquare [2,6]。每个数组的值就是是否存在方块

的标志,存在为1,不存在时缺省为0。这下你该明白,所谓的碰撞判断,其实就是什

么?”

“我知道了,是否能左移,就是判断arraySquare [x,y]中的x–1是否小于0,否则就撞

墙了。或者arraySquare [x–1,y]是否等于1,否则就说明左侧有堆积的方块。所谓堆积,

不过是判断arraySquare [x,y+1]是否等于1的过程,如果是,则将自己arraySquare [x,y]

的值改1。那么消层,其实就是arraySquare [x,y]中循环x由0到9,判断arraySquare [x,y]

是否都等于1,是则此行数据清零,并将其上方的数组值遍历下移一位。”

“那你就应该明白了,所谓游戏逻辑,不过就是数组的每一项值变化的问题,下落、

旋转、碰撞判断、移动、堆积这些都是在做数组具体项的值的变化。而界面表示逻辑,不

过是根据数组的数据进行绘出和擦除,或者根据键盘命令调用数组的相应方法进行改变。

因此,至少应该考虑将此程序分为两个类,一个是游戏逻辑的类,一个是WinForm窗体的

类。当有一天要改变界面,或者换界面时,不过是窗体类的变化,和游戏逻辑无关,以此

达到复用的目的。”

“这个听起来容易,真正要做起来还是有难度的哦!”

“当然,软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离**[ASD]**

。其实要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机

去改变一个类,那么这个类就具有多于一个的职责**[ASD]** ,就应该考虑类的职责分离。”

“的确是这样,界面的变化是和游戏本身没有关系的,界面是容易变化的,而游戏逻

辑是不太容易变化的,将它们分离开有利于界面的改动。”

3.6 手机职责过多吗?

“这下你知道你的手机为什么不能拍摄好UFO的原因了吧?”大鸟笑道。

“如果手机只用来接听电话,DV用来拍摄,职责的分离是可以把事情做得更好。不过

这其实不是一回事哦,现在的智能手机承担的职责多,并不等于就不可以做好,只不过现

在的科技还不能让手机在摄像时超过DV而已。”小菜分析说。

“整合当然是一种很好的思想。比如Google最初的理想就是将一切的需求都整合到一

个文本框里提交,用干净的页面来吸引用户,导致互联网的一场变革。但现在分类信息、

垂直搜索又开始流行,这却是单一职责的思想体现。现在智能手机整合了很多功能的原因

是因为DV、DC、MP3等产品的体积也太大了。手机携带很方便,所以才有了这样的过渡

产品,如果,每一样数码产品都缩小100倍,就像放在包里的一张卡片、一支笔那么简

单,而功能和质量都不发生变化,你还会觉得它们很麻烦吗?”大鸟总结道,“总的来说,

手机的发展有它的特点,而编程时,我们却是要在类的职责分离上多思考,做到单一职

责,这样你的代码才是真正的易维护、易扩展、易复用、灵活多样。”

第4章 考研求职两不误——开放-封闭原则

4.1 考研失败

时间: 3月5日20点 地点: 小菜房间 人物: 小菜、大鸟

“……多少次迎着冷眼与嘲笑,从没有放弃过心中的理想,一刹那恍惚,若有所失的

感觉,不知不觉已变淡心里爱(谁明白我)……”

小菜此时正关在房中坐在桌前发呆,音箱中大声地放着Beyond乐队的《海阔天

空》。此时有人敲门。打开一看,原来是大鸟。

大鸟:“小菜,怎么听这么伤感的歌,声音这么大,我在隔壁都听得清清楚楚。发生

什么事了?”

小菜:“今天研究生考试成绩出来了,我的英语成绩离分数线差两分。之前的努力白

费了。”

大鸟:“失败也是正常的,考不上的人还是占多数呀,想开些吧,找到好工作未必比

读研要差的。”

小菜:“为了考研,我没有做任何求职的准备,所以我们班不少同学都找到工作了,

我却才刚开始,前段时间的面试也没消息。”

大鸟:“哈,鱼和熊掌岂能兼得,为一件事而放弃另一些机会,也是在情理之中的

事。”

小菜:“说是这么说,我却感觉比较难受,我的同学,有几个其实水平不比我强,他

们都签了XX大集团、XX知名公司,而我现在一无所有,感觉很糟糕。要是当时我也花点

时间在简历上,或许现在也不至于这么不爽。”

大鸟:“你考研复习的时候,每天学习多长时间,有没有休息的时候?”

小菜:“差不多十小时吧,其实效率并不高,有不少时候都困得不行,趴在桌上睡觉

去了。”

大鸟:“这就对了,你为什么不利用休息的时间考虑一下自己的简历如何写,关心一

下有些什么单位在招聘呢?这样也就不至于现在这样唉声叹气。”

小菜:“我感觉找工作会影响复习的精力,所以干脆什么都没找,但其实每天都会有

些同学求职应聘的消息传到我耳朵里,我也没有安心复习。”

大鸟:“小菜呀,你其实就是没有搞懂一个设计模式的原则。”

小菜:“哦,是什么原则?”

大鸟:“先不谈这个原则,你想想看,香港澳门的顺利回归,有一个人起了重要的作

用,他是谁?”

小菜:“啊,那还用说,是邓小平呀,如果不是他老人家提出的一国两制思想,或许

现在还没回归呢。”

大鸟:“小平同志的确是伟大的政治思想家,他的这一创造性想法有什么独到之处?”

小菜:“我想想看,原因主要是在于大陆的社会主义制度不能修改,这一点毋庸置

疑,而香港澳门长期在资本主义制度下管理和发展,所以回归时强行修改香港澳门的制度

也并不合理,所以用‘一国两制’来解决制度差异造成的矛盾是最合理的办法。”

大鸟:“说得好,社会主义制度不能修改,邓小平在和英国首相撒切尔夫人谈香港问

题的时候,如果咬定香港回来必须要实现社会主义制度,那回归就困难重重了,香港老百

姓也不答应呀,毕竟这么多年来的殖民统治,突然在整个管理制度上进行彻底变化也是不

现实的,那么怎么办?为了回归的大局,增加一种制度又何尝不可,一个国家,两种制

度,这在政治上,是伟大的发明哦。在软件设计模式中,这种不能修改,但可以扩展的思

想也是最重要的一种设计原则,它就是开放-封闭原则(The Open-Closeed Principle,简称

OCP)或叫开-闭原则。”

4.2 开放**-**封闭原则

小菜:“开放、封闭,具体怎么解释呢?”

大鸟:“这个原则其实是有两个特征,一个是说**‘**对于扩展是开放的(Open for

extension ,另一个是说**‘对于更改是封闭的(Closed for modification’[ASD]** 。”

大鸟:“我们在做任何系统的时候,都不要指望系统一开始时需求确定,就再也不会

变化,这是不现实也不科学的想法,而既然需求是一定会变化的,那么如何在面对需求的

变化时,设计的软件可以相对容易修改,不至于说,新需求一来,就要把整个程序推倒重

来。怎样的设计才能面对需求的改变却可以保持相对稳定,从而使得系统可以在第一个

版本以后不断推出新的版本呢?[ASD] ,开放-封闭给我们答案。”

小菜:“我明白了,你的意思是说,设计软件要容易维护又不容易出问题的最好的办

法,就是多扩展,少修改?”

大鸟:“是的,比如说,我是公司老板,我规定,九点上班,不允许迟到。但有几个

公司骨干,老是迟到。如果你是老板你怎么做?”

小菜:“严格执行考勤制度,迟到扣钱。”

大鸟:“你倒是够狠,但实际情况是,有的员工家离公司太远,有的员工每天上午要

送小孩子上学,交通一堵就不得不迟到了。”

小菜:“这个,让他们有特殊原因的人打报告,然后允许他们迟到。”

大鸟:“哈,谈何容易,别的不迟到的员工不答应了呀,凭什么他能迟到,我就不

能,大家都是工作,我上午也完全可以多睡会再来。”

小菜:“那怎么办?老是迟到的确也不好,但不让迟到也不现实。家的远近,交通是

否堵塞也不是可以控制的。”

大鸟:“仔细想想,你会发现,其实迟到不是主要问题,每天保证8小时的工作量是老

板最需要的,甚至8小时工作时间也不是主要问题,业绩目标的完成或超额完成才是最重

要的指标,于是应该改变管理方式,比如弹性上班工作制,早到早下班,晚到晚下班,或

者每人每月允许三次迟到,迟到者当天下班补时间等等,对市场销售人员可能就更加以业

绩为标准,工作时间不固定了——这其实就是对工作时间或业绩成效的修改关闭,而对时

间制度扩展的开放。”

小菜:“这就需要老板自己很清楚最希望达到的目的是什么,制定的制度才最合理有

效。”

大鸟:“对的,用我们古人的理论来说,管理需要中庸之道。”

4.3 何时应对变化

小菜:“啊,有道理。所以,我们尽量应在设计时,考虑到需求的种种变化,把问题

想得全了,就不会因为需求一来,手足无措。”

大鸟:“哪有那么容易,如果什么问题都考虑得到,那不就成了未卜先知,这是不可

能的。需求时常会在你想不到的地方出现,让你防不胜防。”

小菜:“那我们应该怎么做?”

大鸟:“开放-封闭原则的意思就是说,你设计的时候,时刻要考虑,尽量让这个类是

足够好,写好了就不要去修改了,如果新需求来,我们增加一些类就完事了,原来的代码

能不动则不动。”

小菜:“这可能做到吗?我深表怀疑呀,怎么可能写完一个类就再也不改了呢?”

大鸟:“你说得没错,绝对的对修改关闭是不可能的。无论模块是多么的**‘封闭’**,都

会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模

块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造

抽象来隔离那些变化**[ASD]** 。”

小菜:“那还是需要猜测程序可能会发生的变化,猜对了,那是成功,猜错了,那就

完全走到另一面去了,把本该简单的设计做得非常复杂,很不划算呀。而且事先猜测,这

又是很难做到的。”

大鸟:“你说得没错,我们是很难预先猜测,但我们却可以在发生小变化时,就及早

去想办法应对发生更大变化的可能。也就是说,等到变化发生时立即采取行动**[ASD]** 。

正所谓,同一地方,摔第一跤不是你的错,再次在此摔跤就是你的不对了。”

大鸟:“在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建

抽象来隔离以后发生的同类变化**[ASD]** 。比如,我之前让你写的加法程序,你很快在一

个client类中就完成,此时变化还没有发生。然后我让你加一个减法功能,你发现,增加

功能需要修改原来这个类,这就违背了今天讲到的‘开放-封闭原则’,于是你就该考虑重构

程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承,多态等来隔离具体加

法、减法与client耦合,需求依然可以满足,还能应对变化。这时我又要你再加乘除法功

能,你就不需要再去更改client以及加法减法的类了,而是增加乘法和除法子类就可。即

面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码**[ASD]** 。这

就是‘开放-封闭原则’的精神所在。”(样例代码见第1章)

大鸟:“当然,并不是什么时候应对变化都是容易的。我们希望的是在开发工作展开

不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长,要创建正确的抽

象就越困难**[ASD]** 。”

小菜:“这个我能理解,如果加减运算都在很多地方应用了,再考虑抽象、考虑分

离,就很困难。”

大鸟:**“开放-**封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对

象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该

仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都

刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要**[ASD]** 。

切记,切记。”

小菜:“哦,我还以为尽量地抽象是好事呢,看来过犹不及呀。”

4.4 两手准备,并全力以赴

大鸟:“回过头来说,你考研和求职这两件事,考研是你的追求,希望考上研究生,

可以更上一层楼,有更大的发展空间和机会。所以考研之前,学习计划是不应该更改,雷

打不动的。这就是对修改关闭。但你要知道,你几个月来只埋头学习,就等于放弃了许多

好公司来你们学校招聘的机会,这机会的失去是很不值得的。我就不信你一天到晚全在学

习,那样效果也不会好。所以你完全可以抽出一点时间,在不影响你复习的前提下,来写

写自己的简历,来了解一些招聘大学生的公司的资讯,这不是很好的事吗?既不影响你考

研,又可以增大找到好工作的可能性。为考研万一失败后找工作做好了充分的准备。这就

是对扩展开放,对修改关闭的意义。”

小菜:“是的,我就不信,我会比别人差!”

大鸟笑了笑说:“好了,我回房间去了,你也早些休息吧。”站起身走出了小菜的房

门,此时Beyond的音乐再次响起,大鸟回头,伸出右手向前摆了个“V”字,说了声:“海

阔天空,加油!”

“今天我寒夜里看雪飘过,怀着冷却了的心窝漂远方,风雨里追赶,雾里分不清影

踪,天空海阔你与我可会变(谁没在变),…………仍然自由自我,永远高唱我歌,走遍

千里!”(作者注:本故事和“开放-封闭原则”对应有些牵强,所以在此做一声明。全力以

赴当然是必需,两手准备也是灵活处事的表现,希望读者您能对痛苦关闭,对快乐开

放。)

第5章 会修电脑不会修收音机?——依赖倒转原则

5.1 MM请求修电脑

时间: 3月12日 19点 地点: 小菜大鸟住所的客厅 人物: 小菜、大鸟、娇娇

小菜和大鸟吃完晚饭后,在一起聊天。

此时,突然声音响起。

“死了都要爱,不淋漓尽致不痛快,感情多深只有这样,才足够表白。死了都要

爱……”

原来是小菜的手机铃声,大鸟吓了一跳,说道:“你小子,用这歌做铃声,吓唬人

啊!这要是在公司开大会时响起,你要被领导淋漓尽致爱死!MD,还在唱,快接!”

小菜很是郁闷,拿起手机一看,一个美女来的电话,脸色由阴转晴,马上接通了手

机:“喂!”

“小菜呀,我是娇娇,我电脑坏了,你快点帮帮我呀!”手机里传来急促的女孩声音。

“哈,是你呀,你现在好吗?最近怎么不和我聊天了?”小菜慢条斯理地说道。

“快点帮帮我呀,电脑不能用了啊!”娇娇略带哭腔地说。

“别急别急,怎么个坏法?”

“每次打开QQ,一玩游戏,机器就死了。出来蓝底白字的一堆莫名其妙的英文,过一

会就重启了,再用QQ还是一样。怎么办呀?”

“哦,明白了,蓝屏死机吧,估计内存有问题,你的内存是多少兆的?”

“什么内存多少兆,我听不懂呀,你能过来帮我修一下吗?”

“啊,你在金山,我在宝山,虽说在上海这两地名都钱味儿十足,可两山相隔万重路

呀!现在都晚上了,又是星期一,周六我去你那里帮你修吧!”小菜无奈地说。

“要等五天那不行,你说什么蓝屏?怎么个修法?”娇娇依然急不可待。

“蓝屏多半是内存坏了,你要不打开机箱看看,或许有两个内存,可以拔一根试试,

如果只有一根内存,那就没戏了。”

“机箱怎么打开呢?”娇娇开始认真起来。

“这个,你找机箱后面,四个角应该都有螺丝,卸掉靠左侧边上两个应该就可以打开

左边盖了。”小菜感觉有些费力,远程手机遥控修电脑,这是头一次。

“我好像看到了,要不先挂电话,我试试看,打开后再打给你。”

“哦,好的。”小菜正说着,只听娇娇边嘟囔着“老娘就不信收拾不了你这破电脑”边挂

掉了电话。

“呵!”小菜长出一口气,“不懂内存为何物的美眉修电脑,强!”

“你小子,人家在困难时刻想得到你,说明心中有你,懂吗?这是机会!”大鸟说道。

“这倒也是,这小美眉长得蛮漂亮的,我看过照片。就是脾气大些,不知道有没有男

朋友了。”

“切,你干吗不对她说,‘你可以找男友修呀’,真是没脑子,要是有男友,就算男友

不会修也要男友找人搞定,用得着找你求助呀,笨笨!”大鸟嘲笑道,“你快把你那该死的

手机铃声换掉——死了都要爱,死了还爱个屁!”

“噢!知道了。”

5.2 电话遥控修电脑

十分钟后。

“我在这儿等着你回来,等着你回来,看那桃花开。我在这儿等着你回来,等着你回

来,把那花儿采……”小菜的手机铃声再次响起。

“菜花痴,你就不能找个好听的歌呀。”大鸟气着说道。

“好好好,我一会改,一会改。”小菜拿起手机,一副很听话的样子,嘴里却跟着

哼“我在这儿等着你回来哎”,把手机放到耳边。

“小菜,我打开机箱了,快说下一步怎么走!”娇娇仍然着急着说。

“你试着找找内存条,大约是10公分长,2公分宽,上有多个小长方形集成电路块的长

条,应该是竖插着的。”小菜努力把内存条的样子描述得容易理解。

“我看到一个风扇,没有呀,在哪里?”娇娇说道,“哦,我找到了,是不是很薄,很

短的小长条?咦,怎么有两根?”

“啊,太好了,有两根估计就能解决问题了,你先试着拔一根,然后开机试试看,如

果还是死机,再插上,拔另一根试,应该总有一根可以保证不蓝屏。”

“我怎么拨不下来呢?”

“旁边有卡子,你扳开再试。”

“嗯,这下好了,你别挂,我这就重启看看。”

五分钟后。

“哈,没有死机了啊,小菜,你太厉害了,我竟然可以修电脑了,要我怎么感谢你

呢!”娇娇兴奋 地说。

“做我女朋友吧,”小菜心里这么遐想着,口中却谦虚地说:“不客气,都是你聪明,

敢自己独自打开机箱修电脑的女孩很少的。你把换下的内存去电脑城换掉,就可以了。”

“我不懂的,要不周六你帮我换?周六我请你吃饭吧!”

“这怎么好意思——你说在什么时间在哪碰面?”小菜假客气着,却不愿意放弃机会。

“周六下午5点在徐家汇太平洋数码门口吧。”

“好的,没问题。” “今天真的谢谢你,那就先Bye-Bye了!” “嗯,拜拜!”

5.3 依赖倒转原则

“小菜走桃花运了哦,”大鸟有些羡慕道,“那铃声看来有些效果,不过还是换掉吧,

俗!”

“嘿嘿,你说也怪,修电脑,这在以前根本不可能的事,怎么就可以通过电话就教会

了,而且是真的修到可以用了呢。”

“你有没有想过这里的最大原因?”大鸟开始上课了。

“蓝屏通常是内存本身有问题或内存与主板不兼容,主板不容易换,但内存更换起来

很容易。”

“如果是别的部件坏了,比如硬盘,显卡,光驱等,是否也只需要更换就可以了?”

“是呀,确实很方便,只需要懂一点点计算机知识,就可以试着修电脑了。”

“想想这和我们编程有什么联系?”

“你的意思又是——面向对象?”

“嗯,说说看,面向对象的四个好处?”

“这个我记得最牢了,就是活字印刷那个例子呗。是可维护、可扩展、可复用和灵活

性好。哦,我知道了,可以把PC电脑理解成是大的软件系统,任何部件如CPU、内存、

硬盘、显卡等都可以理解为程序中封装的类或程序集,由于PC易插拔的方式,那么不管

哪一个出问题,都可以在不影响别的部件的前提下进行修改或替换。”

“PC电脑里叫易插拔,面向对象里把这种关系叫什么?”

“应该是叫强内聚、松耦合吧。”

“对的,非常好,我们电脑里的CPU全世界也就是那么几家生产的,大家都在用,但

却不知道Intel、AMD等公司是如何做出这个精密的小东西的。去年国内不是还出现了汉

芯造假的新闻吗!这就说明CPU的强内聚的确是强。但它又独自成为了产品,在千千万万

的电脑主板上插上就可以使用,这是什么原因?”大鸟又问。

“因为CPU的对外都是针脚式或触点式等标准的接口。啊,我明白了,这就是接口的

最大好处。CPU只需要把接口定义好,内部再复杂我也不让外界知道,而主板只需要预留

与CPU针脚的插槽就可以了。”

“很好,你已经在无意的谈话间提到了面向对象的几大设计原则,比如我们之前讲过

的单一职责原则,就刚才修电脑的事,显然内存坏了,不应该成为更换CPU的理由,它们

各自的职责是明确的。再比如开放-封闭原则,内存不够只要插槽足够就可以添加,硬盘

不够可以用移动硬盘等,PC的接口是有限的,所以扩展有限,软件系统设计得好,却可

以无限地扩展。这两个原则我们之前都已经提过了。这里需要重点讲讲一个新的原则,叫

依赖倒转原则 ,也有翻译成依赖倒置原则的。”大鸟接着讲道,“依赖倒转原则,原话解

释是抽象不应该依赖细节,细节应该依赖于抽象 ,这话绕口,说白了,就是要针对接口

编程,不要对实现编程 ,无论主板、CPU、内存、硬盘都是在针对接口设计的,如果针

对实现来设计,内存就要对应到具体的某个品牌的主板,那就会出现换内存需要把主板也

换了的尴尬。你想在小MM面前表现也就不那么容易了。所以说,PC电脑硬件的发展,和

面向对象思想发展是完全类似的。这也说明世间万物都是遵循某种类似的规律,谁先把握

了这些规律,谁就最早成为了强者。”

“为什么要叫倒转呢?”小菜问道。

“这里面是需要好好解释一下,面向过程的开发时,为了使得常用代码可以复用,一

般都会把这些常用代码写成许许多多函数的程序库,这样我们在做新项目时,去调用这些

低层的函数就可以了。比如我们做的项目大多要访问数据库,所以我们就把访问数据库的

代码写成了函数,每次做新项目时就去调用这些函数。这也就叫做高层模块依赖低层模

块。”

“嗯,是这样的,我以前都是这么做的。这有什么问题?”

“问题也就出在这里,我们要做新项目时,发现业务逻辑的高层模块都是一样的,但

客户却希望使用不同的数据库或存储信息方式,这时就出现麻烦了。我们希望能再次利用

这些高层模块,但高层模块都是与低层的访问数据库绑定在一起的,没办法复用这些高层

模块,这就非常糟糕了。就像刚才说的,PC里如果CPU、内存、硬盘都需要依赖具体的

主板,主板一坏,所有的部件就都没用了,这显然不合理。反过来,如果内存坏了,也不

应该造成其他部件不能用才对。而如果不管高层模块还是低层模块,它们都依赖于抽象,

具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其他受

到影响,这就使得无论高层模块还是低层模块都可以很容易地被复用。这才是最好的办

法。”

“为什么依赖了抽象的接口或抽象类,就不怕更改呢?”小菜依然疑惑,“不好意思,

我有些钻牛角尖了。”

“没有,没有,在这里弄不懂是很正常的,原因是我少讲了一个设计原则,使得你产

生了困惑,这个原则就是里氏代换原则。”

5.4 里氏代换原则

“里氏代换原则是Barbara Liskov女士在1988年发表的 [ASD],具体的数学定义比较复

杂,你可以查相关资料,它的白话翻译就是一个软件实体如果使用的是一个父类的话,

那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软

件里面,把父类都替换成它的子类,程序的行为没有变化, 简单地说,子类型必须能够

替换掉它们的父类型[ASD]。”

“这好像是学继承时就要理解的概念,子类继承了父类,所以子类可以以父类的身份

出现。”

“是的,我问你个问题,如果在面向对象设计时,一个是鸟类,一个是企鹅类,如果

鸟是可以飞的,企鹅不会飞,那么企鹅是鸟吗?企鹅可以继承鸟这个类吗”

“企鹅是一种特殊的鸟,尽管不能飞,但它也是鸟呀,当然可以继承。”

“哈,你上当了,我说的是在面向对象设计时,那就意味着什么呢?子类拥有父类所

有非private的行为和属性。鸟会飞,而企鹅不会飞。尽管在生物学分类上,企鹅是一种

鸟,但在编程世界里,企鹅不能以父类——鸟的身份出现,因为前提说所有鸟都能飞,而

企鹅飞不了,所以,企鹅不能继承鸟类。”

“哦,你的意思我明白了,我受了直觉的影响。小时候上课时老师一再强调,像鸵

鸟、企鹅等不会飞的动物也是鸟类。”

“也正因为有了这个原则,使得继承复用成为了可能,只有当子类可以替换掉父类,

软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增

加新的行为。 比方说,猫是继承动物类的,以动物的身份拥有吃、喝、跑、叫等行为,

可当某一天,我们需要狗、牛、羊也拥有类似的行为,由于它们都是继承于动物,所以除

了更改实例化的地方,程序其他处不需要改变。”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dk6XJgWF-1691736442799)(https://gitee.com/Jschrodinger/typora-image/raw/master/9dcc1218e3f64ebf059039637fcac5c3.png)]

“我的感觉,由于有里氏代换原则,才使得开放-封闭成为了可能。”小菜说。

“这样说是可以的,正是由于子类型的可替换性才使得使用父类类型的模块在无需修

改的情况下就可以扩展。 不然还谈什么扩展开放,修改关闭呢。再回过头来看依赖倒转

原则,高层模块不应该依赖低层模块,两个都应该依赖抽象,对这句话你就会有更深入的

理解了。”

“哦,我明白了,依赖倒转其实就是谁也不要依靠谁,除了约定的接口,大家都可以

灵活自如。还好,她没有问我如何修收音机,收音机里都是些电阻、三极管,电路板等等

东东,全都焊接在一起,我可不会修的。”小菜庆幸道。

5.5 修收音机

“哈,小菜你这个比方打得好,”大鸟开心地说,“收音机就是典型的耦合过度,只要

收音机出故障,不管是没有声音、不能调频,还是有杂音,反正都很难修理,不懂的人根

本没法修,因为任何问题都可能涉及其他部件,各个部件相互依赖,难以维护。非常复杂

的PC电脑可以修,反而相对简单的收音机不能修,这其实就说明了很大的问题。当然,

电脑的所谓修也就是更换配件,CPU或内存要是坏了,老百姓是没法修的。现在在软件世

界里,收音机式的强耦合开发还是太多了,比如前段时间某银行出问题,需要服务器停机

大半天的排查修整,这要损失多少钱。如果完全面向对象的设计,或许问题的查找和修改

就容易得多。依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写程序不重

要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依

赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计

了**[ASD]** 。”

“是的是的,我听说很多银行目前还是纯C语言的面向过程开发,非常不灵活,维护

成本是很高昂的。”

“那也是没办法的,银行系统哪是说换就换的,所以现在是大力鼓励年轻人学设计模

式,直接面向对象的设计和编程,从大的方向上讲,这是国家大力发展生产力的很大保障

呀。”

“大鸟真是高瞻远瞩呀,我对你的敬仰犹如滔滔江水,连绵不绝!”小菜怪笑道,“我

去趟WC”。

“浪奔,浪流,万里江海点点星光耀,人间事,多纷扰,化作滚滚东逝波涛,有泪,

有笑…………”

“小菜,电话。小子,怎么又换成新上海滩的歌了,这歌好听。”大鸟笑道,“刚才是

死了都要爱,现在是为爱复仇而死。你怎么找的歌都跟爱过不去呀。快点,电话,又是刚

才那个叫娇娇的小MM的。”

“来了来了,尿都只尿了一半!”小菜心急地接起电话,“喂!”

“小菜呀,我家收音机坏了,你能不能教我修修呢!”

第6章 穿什么有这么重要?——装饰模式

6.1 穿什么有这么重要?

时间: 3月16日20点 地点: 大鸟房间 人物: 小菜、大鸟

“大鸟,明天我要去见娇娇了,你说我穿什么去比较好?”小菜问大鸟道。

“这个你也来问我,干脆我代你去得了。”大鸟笑言。

“别开玩笑,我是诚心问你的。”

“哈哈,小菜呀,你别告诉我说四年大学你都没和女生约过会?”

“嗨!谁叫我念的是理工科大学呢,学校里本来女生就少,所以这些稀有宝贝们,大

一时早就被那些老手们主动出击搞定了,我们这些恋爱方面的小菜哪还有什么机会,一不

小心就虚度了四年。”小菜突生伤感,叹气摇头,并小声的唱了起来,“不是我不小心,只

是真情难以寻觅,不是我存心故意,只因无法找到良机……”

“喂!”大鸟打断了小菜,“差不多就行了,感慨得没完没了。说正事,你要问我什

么?”

“哦,你说我穿什么去见娇娇比较好?”

“那要看你想给人家什么印象?是比较年轻,还是比较干练;是比较颓废,还是要比

较阳光;也有可能你想给人家一种极其难忘的印象,那穿法又大不一样了!”

“你这话怎么讲?”

“年轻,不妨走点嘻哈路线,大T恤、垮裤、破球鞋,典型的年轻人装扮。”

“啊,这不是我喜欢的风格,我从来也没这样穿过。”

“那就换一种,所谓干练,就是要有外企高级白领的样,黑西装、黑领带、黑墨镜、

黑皮鞋……”

“你这叫白领?我看是黑社会。不行不行。”

“哈,来颓废的吧,颓废其实也是一种个性,可以吸引一些喜欢叛逆的女生。一

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值