亲爱的朋友, 欢迎你来到对象村, 开始走进设计模式的世界. 这里的每个人都很熟练的使用设计模式, 很快我和你们一起, 都会学习的很好, 通过设计模式, 跻身上流社会.
计划每一章节的学习, 通过几个篇幅来完成, 理论 + 实践的方式. 书中很多地方用到了图形表示, 小编尽量用图文的方式和大家互动. 先用理论建立知识, 再用图形象地描述巩固学习. 每篇文章给出书中的思考题和大家互动, 在后文给出答案. 力争让没读过此书的朋友也能有个理解. 小编第一次尝试书本跟读, 希望大家多给意见, 同大家一起进步.
记得大学的时候, C++ 的老师在教我们面向对象的是就是用的鸭子会飞, 会叫的例子, 没想到《Head First 设计模式》里用的也是这个, 没看过太多的书, 可能鸭子比较特殊吧. 前几个月, 还出现各种鸭子的表情包, 还有很出名的小黄鸭, 让我不禁赞叹, 鸭子才是人类比较好的朋友呀.
那么第一个问题来了, 一家公司想要设计一款关于鸭子的游戏, 游戏中会出现各种鸭子, 一边游泳戏水, 一边呱呱叫. 游戏的模型已经有了面向对象的思想, 设计了一个鸭子的超类 (Superclass), 并让各种鸭子继承这个超类. 现在想让鸭子会飞, 你会怎么做呢? 初始的鸭超类如下, 随着我们一步步学习, 我们将这个超类一步步优化, 敬请期待.
- public class Duck {
- public Duck() {
- }
- // 游泳
- public void swim() {
- System.out.println("I can swim");
- }
- // 呱呱叫
- public void quack() {
- }
- // 显示
- public void display() {
- }
- // 飞行
- public void fly() {
- }
- }
我第一个冒出来的想法就是继承, 我也算是写了几年程序的老鸟了, 没想到也只能想到这么 low 的想法, 用继承来解决问题. 在继承中加入 fly() 方法. 好啦, 现在所有的鸭子都会飞了, 包括玩具鸭, 天呐, 这是什么情况, 难道你还要给玩具鸭装发动机么. 要知道, 并不是所有的鸭子都会飞的呀. 如果以后还有一只木头鸭, 啥都不会, 怎么办呢. 或者说以后鸭子变异了, 多了功能, 也并不是所有的鸭子都需要有的功能呢. 这就引出了继承的弊端, 给大家留的第一个思考题, 继承会有哪些缺点呢? 期待在留言区看到你的答案 (答案下期揭晓). 以下哪几个是缺点:
A 代码在多个子类中重复
B 运行时的行为不容易改变
C 我们不能让鸭子跳舞
D 很难知道所有鸭子的全部行为
E 鸭子不能同时又飞又叫
F 改变会牵一发动全身, 造成其他鸭子不想要的改变
所以, 我们还是不能用继承来解决刚提出来的问题. 有些鸭子会飞, 有些鸭子会叫, 有些啥都不会, 鸭子行为太过于繁杂, 不能一以贯之. 这就引出了我们需要接触到的设计原则「找到应用中可能需要变化之处, 并把它们独立出来, 不要和那些不需要变化的代码混在一起」这样, 代码变化引起的不经意后果变少, 系统变得更有弹性. 在刚才的例子中, 我们就把鸭子会飞以及会叫的属性的分开, 组建一个新类来表示.
第二个重点来了, 如何设计那组实现飞行和呱呱叫的行为的类呢. 那就把鸭子的行为放在分开的类中, 此类专门提供某行为接口的实现, 这样鸭子类就不再需要知道行为的实现细节. 鸭子的子类将实现接口, 不会绑死在鸭子的子类中, 做一些没必要的事情. 这就是我们需要说的第二个设计原则「针对接口编程, 而不是实现编程」
我们利用接口代表每个行为, 在这里用 FlyBehavior 与 QuackBehavior 分别表示飞行行为和呱呱叫行为, 并且行为的每个实现都将实现其中的一个接口. 这样的好处就是飞行和呱呱叫的动作可以被其他对象复用, 复用的同时这些行为彻底和鸭子类无关. 前文说到的, 鸭子还想增加其他的行为, 也可以通过此方式来进行, 既不会影响到既有的行为类, 也不会影响到使用到飞行行为的鸭子类. 整合之前鸭子的行为, 在实践中就是把飞行和呱呱叫方法在之前的超类中删除, 通过接口来实现, 进行分离.
飞行和呱呱叫接口如下:
- // 飞行接口
- public interface FlyBehavior {
- public void fly();
- }
- // 呱呱叫接口
- public interface QuackBehavior {
- public void quack();
- }
相应的实现类如下:
- // 飞行类
- public class FlyWithWings implements FlyBehavior {
- @Override
- public void fly() {
- // 实现鸭子的飞行
- System.out.println("I'm flying!!!");
- }
- }
- // 呱呱叫类
- public class Quack implements QuackBehavior {
- @Override
- public void quack() {
- // 实现鸭子的呱呱叫
- System.out.println("Quack");
- }
- }
如果鸭子要动态设定行为呢? 什么意思, 就是说「模型鸭想飞, 利用火箭动力飞」. 在我们现有的设计中, 难不倒我们. 我们可以在鸭子子类中通过设定方法「setter method」的方式来设定鸭子的行为, 而不用在构造器中实例化. 构造器中模型鸭不会飞, 通过设定鸭子的行为, 把火箭动力行为设定好, 相当于把鸭子加了一个 bug, 设定好之后再给鸭子去飞.
- // 在超类中添加动态设置的行为
- public void setFlyBehavior(FlyBehavior fb) {
- flyBehavior = fb;
- }
- public void setQuackBehavior(QuackBehavior qb) {
- quackBehavior = qb;
- }
- // 通过一个测试类进行测试反馈
- public class MiniDuckSimulator {
- public static void main(String[] args) {
- Duck mallard = new MallardDuck();
- // 这里调用 MallardDuck 继承来的 performQuack(), 进而委托给该对象的 QuackBehavior 对象处理,
- // 也就是说, 调用继承来的 QuackBehavior 的 quack(),performFly 同理
- mallard.performQuack();
- mallard.performFly();
- Duck model = new ModelDuck();
- // 第一次调用, 不会飞
- model.performFly();
- // 调用继承来的 setter 方法, 把火箭动力飞行的行为定到模型鸭中, 模型鸭能一飞冲天
- model.setFlyBehavior(new FlyRocketPowered());
- // 这样就成功的改变了行为
- model.performFly();
- }
- }
- // 运行结果, 我们实现了火箭助力飞行
- Quack
- I'm flying!!!
- I can't fly
- I'm flying with a rocket
好了, 现在我们有鸭子超类, 鸭子类, 飞行行为实现 FlyBehavior 接口, 呱呱叫行为实现 QuackBehavior 接口. 之前继承的想法, 就是 IS-A(是一个) 的关系, 现在通过封装行为, 实现接口, 变成了 HAS-A(有一个) 的关系, 将两个类结合起来, 就成为了一个组合 (composition). 区别显而易见, 鸭子的行为是通过对象的「组合」来的, 而不是「继承」而来. 恭喜你, 掌握了第三个设计原则「多用组合, 少用继承」. 使用组合建立系统具有很大的弹性, 只要组合的行为对象符合正确的接口标准, 那就可以「在运行时动态的改变行为」.
简单的学到这里, 其实已经在无形之中引出了一个设计模式「策略模式」, 他定义了算法族, 分别封装起来, 让它们之间互相替换, 此模式让算法的变化独立于使用算法的客户. 有了这个策略模式, 系统不需要担心遇到任何改变, 通过上述的操作, 就能灵活应对, 神不神奇, 意不意外, 惊不惊喜.
现在对之前的理论做个简单的总结: 首先我们先用鸭子举个例子, 引出超类 Duck, 子类的概念; 第二, 因为鸭子需要各自有行为, 呱呱叫, 飞行行为, 我们设计出接口 QuackBehavior 和 FlyBehavior, 实现类 FlyWithWings(实现鸭子飞行) 等其他行为和 Quack(实现鸭子呱呱叫等其他叫声); 第三, 我们需要鸭子有火箭般的速度, 继而引出 FlyRocketPowered 的行为, 方便鸭子动态设定. 自此三个步骤, 把今天的行为实现, 得出了三个设计原则和一个设计模式. 最终的 Duck 类如下
- public abstract class Duck {
- // 为行为接口类型声明两个引用变量, 所有 鸭子类都继承他们
- FlyBehavior flyBehavior;
- QuackBehavior quackBehavior;
- public Duck() {
- }
- public void setFlyBehavior(FlyBehavior fb) {
- flyBehavior = fb;
- }
- public void setQuackBehavior(QuackBehavior qb) {
- quackBehavior = qb;
- }
- abstract void display();
- public void performQuack() {
- // 委托给呱呱叫行为类
- quackBehavior.quack();
- }
- public void performFly() {
- // 委托给飞行行为类
- flyBehavior.fly();
- }
- public void swim() {
- System.out.println("I can swim");
- }
- }
代码流程部分就到这里, 在学习的过程当中, 还有很多类图, 很多思考以及总结等我去完善. 这是第一篇, 下面一篇会把这次的转变通过图文的方式做一个总结, 小伙伴们先别急, 学习是一个循序渐进的过程, 小编会把代码写好, 测试好, 开源到 GayHub 上, 同大家一起进步.
今天的内容就到这里, 欢迎各位拍砖!
为了让小伙伴免去找资料的麻烦, 公众号「奔跑吧攻城狮」回复设计模式, 获取最经典的两本书籍《Head First 设计模式》以及《大话设计模式》, 一同开启新篇章.
来源: https://www.cnblogs.com/dimple91/p/10567893.html