有些人已经解决了你的问题
- 经验复用
设计模式是利用其他开发人员的经验和智慧;
- 使用方式
把模式装进脑子里,寻找如何使用它们;
先从简单的模拟鸭子应用做起
需求:
- 设计一套模拟鸭子的游戏:
游戏中有各种鸭子,一边游泳戏水,一边呱呱叫;
- 如何使用标准OO技术实现?
设计一个超类,并让各种鸭子继承此超类;
设计一个超类
现在我们得让鸭子能飞
- 变动后的需求如何实现?
在 Duck 类中增加一个 fly() 方法
但是,可怕的问题发生了...
- 事实是
并非Duck 所有的子类都会飞,比如橡皮鸭
对代码所做的局部修改,影响层面可不是局部(会飞的橡皮鸭子)
为了“复用”目的使用继承,结局并不完美
橡皮鸭子类(Rubber Duck)
通过继承解决?
利用继承提供Duck行为的缺点
- 缺点:
代码在多个子类中重复
运行时的行为不容易改变 很难知道所有鸭子的全部行为
改变会牵一发动全身,造成其他鸭子不想要的改变
- 后果:
每当有新的鸭子子类出现,都要被迫检查并可能需要覆盖 fly() 和 quark()
利用接口如何?
利用接口优缺点
- 解决的问题
解决了 “并非所有的子类都具有飞行和呱呱叫的行为”
- 带来的问题
造成代码无法复用;
当所有子类都要稍微修改一下飞行的行为时工作量巨大
软件开发的一个不变真理
- 不变真理:
变化
不管当初软件设;计得多好,一段时间后总是需要成长与改变;
把问题归零......
总结:
- 使用继承并不能很好的解决问题
1)鸭子的行为在子类里不断地改变;
2)让所有的子类都有这些行为是不恰当的;
使用接口也不能很好的解决问题
- 接口不具有实现代码,所以继承接口无法达到代码复用;
这意味着:
无论何时修改某个行为,都要往下追踪并在每一个定义侧行为的类中修改它, 容易造成新的错误。
第一个设计原则
设计原则:
找出应用中可能需要变化之处,把他们独立出来, 不要和那些不需要变化的代码混在一起。
解读:
把会变化的部分取出并 “封装” 起来,好让其他部分不会受到影响。
影响:
代码变化引起的不经意后果变少,系统变得更有弹性。
分开变化和不会变化的部分
Duck 类中变化的部分:
fly() 和 quack()
如何分?
建立两组类,一个是 “fly” 相关,一个是 “quack” 相关;
为了要把这两个行为从 Duck 类中分开,我们将把它们从 Duck 类 中取出来,建立两组新类来代表每个行为;
把行为从Duck类中分开
如何设计鸭子的行为?
要求:
1、一切能有弹性;
2、能够“指定”行为到鸭子的实例,换句话说: 在鸭子类中包含设定行为的方法, 这样就可以在“运行时”动态地“改变”鸭子的行为
第二个设计原则
设计原则:
针对接口编程,而不是针对实现编程。
解读:
针对实现编程:
1)行为来自 Duck 超类的具体实现,
2)继承某个接口并由子类自行实现而来;
注: 这两种做法都是依赖于“实现”,我们被实现绑得死死的, 没办法更改行为(除非写更多代码)
针对接口编程:
1)利用接口代表每个行为(如:FlyBehavior 与 QuackBehavior ), 而行为的每个实现都将实现其中的一个接口;
2)由行为类而不是 Duck 类来实现行为接口;
影响:
新设计中,鸭子的子类将使用接口(FlyBehavior 与 QuackBeahvior) 所表示的行为,所以实际的“实现”不会绑死在鸭子的子类中。
实现鸭子的行为
整合鸭子的行为
关键:
鸭子现在会将飞行和呱呱叫的动作“委托”被人处理, 而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
做法:
1)在鸭子类中加入两个示例变量。
2)实现 performQuack();
3)设定 flyBehavior 与 quackBehavior 的实例变量
Duck 类
FlyBehavior 接口及行为类
QuackBehavior 接口及实现类
MallardDuck 类
客户端代码