本文参照《Head First 设计模式》,转载请注明出处
对于整个系列,我们按照这本书的设计逻辑,使用情景分析的方式来描述,并且穿插使用一些问题,总结的方式来讲述。并且所有的开发源码,都会托管到github上。
引文
Joe的公司是做模拟鸭子活动的游戏而出名,这款游戏取名为SimUDuck,这款游戏具有非常多的鸭子,一边游泳一边呱呱叫。这里的设计采用了标准的OO(Object Oriented,面向对象)的方式编写,这里有一个鸭子的超类(SuperClass),后续所有的鸭子都必须继承这个超类。
- OO面向对象模型
- 继承
这时,公司的高层们想要通过模拟会飞的鸭子来追求行业的领先。然后Joe的项目经理拍着胸脯告诉主管,Joe很快就可以搞定,“有了OO什么都不怕”
Joe接收到任务后,想出了一个办法:“我仅需要在Duck这个超类中加上Fly()的方法,然后所有的鸭子都可以飞了”。然后他的设计模型就改成以下的样子
这样看起来貌似没有什么问题的,然后可怕的问题发生了。。。。
用户反馈,自己的橡皮鸭和木鸭居然也可以飞起来!!!
那么,到底是为何导致了这个可怕的问题?
我们来分析一下:由于Joe在Duck超类中加上fly方法,导致所有的子类都会继承该方法,这就导致了原本不会飞的橡皮鸭和木鸭也具有飞行能力,显然为了提高复用性使用的继承方式,并未达到完美得结果。
- 继承+覆盖
Joe在思考后,又得出一个方案,那就是在橡皮鸭中将fly方法覆盖掉,不做任何操作,这样原有的RubberDuck类中架构就变成如下:
然后,业务的需求又需要加入木鸭(DecoyDuck),它不会叫也不会飞。苦逼的Joe又要把木鸭(DecoyDuck)中quark方法覆盖,这样DecoyDuck中的类结构就变成如下:
但是我们会发觉,如果以后有新的业务,甚至于fly方法中出现一个bug,或者需要删除fly相关的业务,所有相关代码都需要修改,在大型项目中,这都导致非常可怕的维护问题。那么,到底该怎么办?
- 接口
“这不就相当于让我根据用户手机壳颜色换主题吗?”,可怜的Joe在快被想要冲上去打产品经理的时候(皮一下。。。)。脑子突然想起一件神器,他决定试试。他的方法就是使用接口,将fly分离出来,放进Flyable接口中。针对于quark方法,也可以这样分离进Quarkable中
虽然这样看起来满足了我们之前所提到的所有需求,但是这样一来重复代码量会非常可怕,如果有上万个duck子类,joe一定会发疯的。所有,这个方法虽然看起来很好,但是一旦某个方法或者行为发生改变,我们需要定位到所有实现该方法的类中去修改对应的代码,这很容易导致bug的发生。
那么,到底有没有一种能够建立软件的有效方法,能够让我们可以对既有的项目在影响最小的情况下修改他的业务逻辑,这样我们能够花很少的时间去修改代码。
2.策略模式
设计原则第一条:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混合在一起。
按照以上的原则进行设计,代码发生变化引起的后果会非常小,整个项目会特别具有弹性。
这个原则不仅仅适用于策略模式,对于之后讲解的模式同样也是核心的指导方向。那么我们继续Joe所遇到的Duck问题。
2.1分开变化的和不会变化的
我们很清楚,Duck类内部的fly()和quark()伴随着鸭子的不同会发生改变,其他模块是不变的。为了要把这两个变化的行为从Duck类中分开,我们把它们从Duck类中抽离出来,建议一组新类用来代表每一个行为。
那么,问题来了。如何设计那组实现飞行行为和呱呱叫行为的一组类呢?
这里就需要提及第二设计原则
第二设计原则
针对于接口编程,不针对实现编程
这里我们希望的是在创建具体的Duck类的时候,可以动态的生成对应的行为。打个比方,我们想要产生一个新的绿头鸭,将制定类型的飞行行为赋予给它。这就说明,在Duck类中,我们需要包含定义Duck行为的方法,这样在运行的时候,我们就可以动态去改变绿头鸭的行为。
所以这里我们使用两个接口来代表两个行为,这里定义为 FlyBehavior和QuarkBehavior,行为的每次实现,都将实现对应的接口。
但是接口类是没有方法体的,也就是说,我们需要一组类实现对应的行为,这些专用来实现类似FlyBehavior和QuarkBehavior的一组类,我们称为行为类。
这里用一个表格来对比以前用Duck实现接口的方式和现在的依赖实现类实现的方式
//todo