设计模式【一】之策略模式for Java And Android
本章设计模式主要针对Java和Android的开发者(根据《Head First 设计模式》学习整理而来):
- 你可以从中学习到策略模式的具体思路和完整理解
第一步:我们先从模拟鸭子应用做起
公司要求做出一套模拟鸭子的应用程序:
游戏中会出现各种鸭子,一边游泳戏水,一边咕咕叫。
此系统的内部设计出一套标准的OO技术,设计了一个鸭子的超类(SuperClass),并让各种鸭子继承此超类。
第二步:现在我们让鸭子能飞
公司产品经理提出一个新需求:
要求我们需要一只会飞的鸭子。技术员工觉得这很简单嘛,我可是一个合格的OO(面向对象)程序猿。
开发员认为:我只需要在Duck()基类中加上一个fly()方法,然后所有的鸭子都能集成fly()。标准的面向对象程序设计,这有什么难的呢?
但是,这样的做法出现了问题
在实际应用中,一个新东西“橡皮鸭”也拥有了飞行能力。这怎么能行呢!
继承不行,试试接口如何?
技术人员觉得,这样的继承,会导致所有的鸭子子类都拥有,飞行fly();和quack();咕咕叫的能力。显然,继承并不能完美的解决问题。想到了为fly和quack单独写一个接口。通过实现这两个接口,去达成目标!
问题解决到这里,似乎已经解决了需求。但是你是否想过:如果有一种软件设计方法,让我们可以用一种对既有的代码影响最小的方式来修改软件该有多好。我们就可以花较少的时间重做代码,而剩余多余的时间去研究更酷的事情…..
软件开发有一个不变的真理。那就是,不管你在何处工作,构建些什么,用何种语言编程,在软件开发这件事情上,一直伴随你的那个不变的真理是什么?
第三步:OK,现在我们来把这个问题归零
现在我们知道了,只是使用继承并不能很好的解决问题,因为鸭子的行为在各种子类里面会不断的变化,并且,让所有的子类都具有相同数量相同规格的行为表现是不恰当的。Flyable和Quackable接口似乎在一开始表现不错,能解决问题(只有会飞的鸭子实现了Flyable接口)。但是,Java的接口不具有实现的代码,所以继承接口无法达到代码的复用。这意味着,无论何时你需要修改某个行为,你必须得往下跟踪并在每一个定义该行为的子类中修改它。一不小心就会造成新的Bug!幸运的是,有一个程序设计原则,加好用于此状况(设计原则:找出应用中的可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混合在一起!)
换句话说:如果每次新的需求到来,都会使某方面的代码发生变化,那么就可以确定,这部分代码需要被抽取出来。和其他的稳定的代码有所区分!
好,讲到这里,我们需要在鸭子这个问题上,把鸭子的行为从Duck类中取出来了!
第四步:分开变化和不会变化的部分
从哪里开始呢?就我们目前所知,除了fly()和quack()的问题以外,Duck类其他还算一切正常,似乎没有特别需要经常变化或者修改的地方。所以,除了某些小的改变之外,我们不打算对Duck类进行太多处理。
现在,为了区分“变化和不变化的部分”,我们准备建立两组类(完全远离Duck类),一个是和fly相关的,一个是和quack相关的。每一组都将实现各自的动作。比方说,我们可能有一个类实现“呱呱叫”,另一个类实现“吱吱叫”,还有一个类实现“不叫(安静)”。
我们知道Duck类中的fly(),和quack()会因为鸭子的不同而有所改变
为了把这两个行为从Duck类中分离出来,我们将把它们从Duck类中取出来,建立一个新的类来代表每个行为
第五步:设计鸭子的行为
如何设计那组实现飞行和咕咕叫的行为的类呢?
我们希望一切都能有弹性,毕竟,正是因为一开始鸭子的行为没有弹性,才导致我们走上现在这条路。我们还想能够“指定”行为给鸭子。比方说,我们想要产生一个新的绿头鸭实例,并“指定”一个特定的飞行行为给这只绿头鸭。干脆顺便让鸭子的行为可以动态改变就好了。换句话说,我们需要在鸭子的基类Duck类中设定一个设置行为的方法。这样就可以在“运行时”动态地“改变”绿头鸭的行为了(比如绿头鸭一开始“飞得超级快”,过了一段时间后“飞得很懒,很慢”)。
有了这个目标,我们需要知道第二个设计原则:“设计原则:针对接口编程,而不是针对实现编程”
我们针对接口编程,设计两个行为,比方:FlyBehavior和QuackBehavior,而行为的每个实现都将实现其中的一个接口。
所以这次鸭子类不会实现Flying接口和Quacking接口。而是由我们制造的一组其他的类专门来实现FlyBehavior和QuackBehavior,这就称为“行为”类。由行为类,而不是Duck类来实现行为接口。
这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都过于依赖于实现,我们被绑得死死的,没有办法更改行为,(除非写更多的代码)
我们的新的设计中,鸭子的子类将使用接口(FlyBehavior和QuackBehavior)所代表的行为,所以实际的“实现”并不会绑死在子类中。
现在有人可能要问:我不懂你为什么非要把FlyBehavior设计成接口,为何不用抽象超类,这样不就可以使用多态了吗?
“针对接口编程”真正的意思是“针对超类型(supertype)编程”
这里所谓的“接口”有很多含义,接口时间一个“概念”,也是一种Java的Interface构造。你可以在不涉及Java interface 的情况下,“针对接口编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时可以根据实际情况执行到真正的行为,不会被绑死到超类型的行为上。“针对超类型编程”这句话,可以更明确的说成“变量的声明应该是超类型”,通常一个抽象类,或者是一个接口,如此,只要是具体实现此超类型的类,所产生的对象,都可以指定给这个变量赋值符号“=”。这也意味着,声明类时不用理会以后执行的时候真正对象的类型!
这可能不是你第一次听到这种做法,但是请务必注意我所说的是同一件事。看看下面这个简单的多态例子:假设有一个抽象类Animal,有两个具体的实现(Dog和Cat)继承自Animal。做法如下:
“针对实现编程”
Dog d = new Dog();
d.bark();
“针对接口编程”
Animal animal = new Dog();
animal.makeSound();
更棒的是,子类实例化动作时不再需要在代码中硬编码,例如new Dog();而是,“在运行时才指定具体实现的对象”。
a = getAnimal();
a.makeSound();
回到鸭子的问题,实现鸭子行为
在此:我们有两个接口,FlyBehavior和QuackBehavior,还有他们对应的类,负责具体的行为:
这样的设计,可以让飞行和咕咕叫的动作被其它的对象复用,因为这些行为已经与鸭子类无关了。更重要的是:我们还可以利用这种方式,新增一些行为,不会影响到既有的行为,也不会影响到“使用”到飞行行为的鸭子类。简直是太妙了。
整合鸭子的行为
关键在于,鸭子现在将飞行和咕咕叫的动作“委托”(delegate)给别人处理,而不是使用定义在Duck类(或者子类)内的呱呱叫和飞行方法。
做法是这样的:
首先,在Duck类中加入两个实例变量(分别属于FlyBehavior和QuackBehavior),这两个都是接口类型,并不是实体类类型。删除原来Duck类中的quack和fly方法,取而代之的是,performFly和performQuack方法。
ok,到这一步,我们来看一看performQuack()怎么实现。
public class Duck{
QuackBehavior quackBehavior;//接口类型QuackBehavior的引用实例
//还有更多不一一列举
...
public void performQuack(){
quackBehavior.quack();//鸭子对象不是亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象.
}
}
很简单对吧,想进行咕咕叫的动作,Duck对象只需要叫QuackBehavior对象去咕咕叫就行了,在这部分代码中,我们不用在乎quackBehavior接口的对象到底是什么,我们只关心该对象怎么叫唤就行了。(仅需要调用performQuack()方法)
更多的代码整合
OK,现在我们来看一看“如何设定FlyBehavior和QuackBehavior的实例变量”。
先看看 MallardDuck(绿头鸭)类:
//绿头鸭类
public class MallardDuck{
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;
public MallardDuck(){
quackBehavior = new Quack();//绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用的时候,叫唤的职责被分配给Quack对象。
flyBehavior = new FlyWithWings();//使用FlyWithWings作为FlyBehavior的类型.
}
public void display(){
System.out.println("I'm a real Mallard Duck!");
}
}
测试Duck代码
- 输入并编译下面的DUck类(Duck.java) 以及前面的MallardDuck类(MallardDuck.java)
动态的设定行为
讲到设计模式
策略模式的正式定义
当你需要给朋友留下更深刻的映像,或者想影响主管的关键性决策时,请使用下面这个“正式”定义:
策略模式
定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
重要事情公示
重要公式
博主的github地址(https://github.com/KellenHu),里面有一些挺好玩的小东西,欢迎来访!