设计模式介绍
先看下面一个例子:
JOB开发了一个模拟鸭子游戏,游戏中会出现各种各样边游戏边呱呱叫的鸭子。该游戏系统采用了标准的OO(object Oriented)技术开发,系统中所有的鸭子都继承与Duck类,核心类图如下:
随着与其他公司的竞争愈发激烈,公司高管认为,游戏需要模拟会飞的鸭子,从而来甩开竞争对手。与是JOB理所当然的在Duck类中添加了个Fly()方法,然后让所有的鸭子都继承这个方法。修改后的核心类图如下:
但是,可怕的事情发生了,在演示中,游戏中新添加的鸭子角色RubberDuck【橡皮鸭子】,在天空飞行,因为在Duck类添加了Fly()方法,所以所有继承Duck类的鸭子都具备了Fly()方法,也使得其他不会飞的鸭子也具备了飞行的能力。
于是,JOB想到了继承,把RubberDuck的Fly()方法覆盖,使它什么也不做。
这样的确能解决眼前的问题,但是如有还添加数十种行为不一样的鸭子子类的时候,我们又在每个鸭子子类中去覆盖Quack()和Fly()等方法?
通过这样继承来提供Durk的行为,做所带来的缺点是:
1.代码在多个子类中复用;(不断重写quack()或fly()等方法来应对改变)
2.难以得知所有鸭子的全部行为,会带来对Duck类经常的改动;
3.Duck类的改变会牵一发而动全身,造成其他鸭子不想要的改变。
JOB认识到继承并不是答案,于是他想到了用接口,把Fly()方法放到Flyable接口中,相应的Quack()方法取出来放到Quackable接口,以后还可能添加其他的行为接口,当有需要该行为的鸭子类,实现该行为接口即可,核心类图:
你觉得这种设计如何?
别忘了,JAVA接口不具有实现代码,所以继承接口无法达到代码复用目的,就是说如果你要修改某一个行为,你要往下追随到该接口的 每一个 实现类去一个一个修改,这显然不是我们想要的结果。
这里我们引入第一条设计原则:
找出应用可能需要改变之处,并把它独立封装起来,不要和不需要变化的代码混合在一起。
在上述的问题,我们可知,鸭子的行为(飞行、叫声)会随着鸭子的不同而改变,所以我们需要把它独立出来,建立一组新类代表每一个行为。比方说,我们需要一个类会呱呱叫,一个类实现吱吱叫,一个类实现安静。
从现在开始,鸭子的行为被分离到了独立的一组类中,这组类用来实现鸭子的行为,也就是说,在鸭子类中我们需要一个设定行为的方法(因为行为和鸭子类本身已经独立分开了)。这样也使得我们可以动态的改变鸭子行为。
为了达到以上目标,我们映入第二条设计原则:
针对接口编程,不要针对实现编程。(这里的接口可以是抽象类和java 接口)
根据这条原则,我们为每一个行为定义一个接口,如为fly行为定义接口FlyBehavior,为Quack行为定义接口QuackBehavior。而这些行为都必须实现其中一个接口。例如如下的fly行为和Quack行为:
在这种设计之下,鸭子的行为由实现FlyBehavior和QuackBehavior接口的实现类来完成,而不会绑死在Duck的子类中。这样的设计可以使得这些行为可以复用,而且增加一个行为,也不会造成其他鸭子子类不必要的改变(当该行为不适用该鸭子,不必要通过重写来配合改变)。
鸭子的行为我们已经解决的,最后我们需要的是整合鸭子的行为。
第一步:我们要给Duck类增加两个接口类型的实例变量,分别是flyBehavior和quackBehavior,它们是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使用多态的方式在运行时获得所需要的行为类型的引用。
第二步:我们还要把fly()和quack()方法从Duck类里移除,因为我们已经把这些行为移到FlyBehavior和QuackBehavior接口里了。我们将使用两个相似的performFly()和performQuack()方法来替换fly()和qucak()方法。
第三步:我们要考虑什么时候初始化flyBehavior和quackBehavior变量。最简单的办法就是在Duck类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法SetFlyBehavior()和SetQuackBehavior(),那么就可以在运行时动态改变鸭子的行为了。
整合后的核心类图:
其中PerformFly()方法只需要这样设置:
preformFly(){ FB.fly(); }
setFlyBehavior(FlyBehavior flybehavior)方法只需要这样设置:
setFlyBehavior(FlyBehavior flybehavior){ FB=flybehavior; }
PerformQuack()方法和SetQuackBehavior(QuackBehavior quackbehavior)与之类似。
当我们需要建立一个不会飞和吱吱叫的橡皮鸭子时,我们只需要:
Duck rubberDuck=new RubberDuck(); rubberDuck.setFlyBehavior(new FlyNoWay()); rubberDuck.setQuackBehavior(new Squeak());
rubberDuck.display(); rubberDuck.preformFly(); rubberDuck.preformQuack();
如果我们需要增加一个新的鸭子子类,和增加一种该鸭子子类的行为,按照以上方法是很容易而且简单的办到。而且不会应该到其他鸭子子类。
到了这里,你已经学会了第一种设计模式:Strategy Pattern(策略模式)
Strategy Pattern定义如下:定义算法族,分别封装起来,让它们之间可以相互替换,此模式让算法独立于使用它的客户而独立变化
(其中算法族,对于上例来说,就是鸭子的一组行为。)
我们还可以从上例学到一个很重要的技巧。上例中,每一个鸭子都有一个FlyBehavior而且有一个QuackBehavior,让鸭子将飞行和叫委托给它们代为处理。当你将两个类结合起来使用,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承而来,而是和适当的行为对象“组合”而来。
这就是我们的第三个设计原则:
多用组合,少用继承。
通过上例的学习,你或者已经对设计模式有一定的了解。
设计模式并不是代码,而是针对设计问题的通用的解决方案。可以使开发人员开发出具有可复用性,可扩充性,可维护性的良好的OO系统,同时模式可以使开发人员之间有共享的语言,最大化沟通的价值。这也是我们学习设计模式的目的。