设计模式大部分都是跟什么接口,抽象类打交道,其实很多设计模式我们平时自觉不自觉的都使用,准备花段时间把设计模式重新学习一篇,结合我买的head first设计模式这本书,感觉这本书写的确实不错,也是想把书上的设计场景通过博客记录下来,而不是单纯的说出这种模式是怎么用的,这样的话总感觉没啥意思,还是希望一步步为什么要使用这种设计模式,优点在哪,使用场景等等,今天讲讲策略模式
OO:Object Oriented(面向对象),java是面向对象的,所以我们脑子里要有这种思维,但是说到思想肯定有一定的学习过程,因为说到思想它是很复杂的,这多是做开发做了2年以上或者才有这种体会,java中OO的思想就是抽象,继承,多态,现在通过场景来一步步讲今天的模式
小明在杭州某个公司上班,这公司主要是做关于儿童游戏的软件,小明的主管叫小明设计一款会游泳戏水和呱呱叫的鸭子,小明做java也有2年多,这个小问题easy,于是小明很快就根据java中的抽象思想,因为每个鸭子都会游泳和呱呱叫,这是所有鸭子的共性,所以把这些共性抽取出来写在父类中,让每个具体的鸭子去继承就可以,不同的就写成抽象方法让子类自己去实现,于是小明花几分钟就把类图画出来了,
swim()是鸭子游泳的行为
quack()是鸭子呱呱叫的行为
display()是鸭子的外观是什么,因为每个鸭子都不一样,因此需要子类自己去实现,既然类图都出来了,那么代码就很easy了
Duck.java(父类)
package com.strategyparrent; /** * Created by admin on 2017/1/24. */ public abstract class Duck { public void swim(){ System.out.print("鸭子游泳"); } public void quack(){ System.out.print("鸭子呱呱叫"); } public abstract void display(); }MallardDuck.java
package com.strategyparrent; /** * Created by admin on 2017/1/24. */ public class MallardDuck extends Duck { @Override public void display() { System.out.print("白色的鸭子"); } }RedHeadDuck.java
package com.strategyparrent; /** * Created by admin on 2017/1/24. */ public class RedHeadDuck extends Duck { @Override public void display() { System.out.print("红色的鸭子"); } }上层使用:
package com.strategyparrent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Duck duck = new RedHeadDuck();//多态 duck.swim(); duck.display(); } }做软件唯一不变的就是不断的需求变化,小明的主管说让鸭子具备飞的功能,小明听了这个需求后,一个小时搞定,太简单了,不就是在父类中再添加一个共有的会发的方法么,太easy,于是小明又跑到座位上先把类图画了下:
就是需要在父类中添加一个fly()方法,子类都不用动,
package com.strategyparrent; import android.util.Log; /** * Created by admin on 2017/1/24. */ public abstract class Duck { public void swim(){ Log.e("Duck","鸭子游泳"); } public void quack(){ Log.e("Duck","鸭子呱呱叫"); } public void fly(){ Log.e("Duck","会飞的鸭子"); } public abstract void display(); }没等一会小明就把代码提交上去了,主管看了一眼,在qq上跟小明说玩具鸭子会飞么,小明此刻陷入了沉思之中,我擦,我咋就没想到呢?主管就是主管,这就是我为什么还是一个码农,小明这样设计的缺陷在于在父类中实现了fly()方法,就相当于所有Duck子类都具备了这个飞的行为,所以虽然小明只是在Duck添加了一个方法,算是做了局部修改吧,但是影响却不是局部的,可能是致命的,这时小明就悟出了一个道题:
为了复用目的而使用继承,结局并不完美
小明心想我可以覆盖父类中(Duck)中的fly()方法,我可以什么逻辑都没有,可是以后还会有变态的需求啊,如果以后要加入诱饵鸭(DecoyDuck),又不会飞又不会叫,难道还要覆盖父类中的方法,然后在方法中什么逻辑也不实现么,觉得这么合理么,哪以后会有更多的鸭子,可能有的鸭子会只会部分父类的行为,哪万一这个时候你新招的员工并不知道,调用了父类的方法,但是这个鸭子并不具备这个行为,这就导致bug产生了,所以使用继承是行不通的,赶快换个思路吧,不然要痛苦死。
小明想了想类和类之间关系除了继承还有啥,突然脑海中想到了还有实现呢,也就是接口,让某些行为独立出来,比如会叫的方法不要放在父类中,而不是定义接口,那个鸭子会叫我就让它实现这个接口,不就哦了,于是小明又去作为上把类图结构画出来了.
这样就把鸭子有的行为给分离出来了,没有直接写在父类中,这样设计也会带来问题的,
第一个问题:如果鸭子有很多行为那么就要定义很多接口
第二个问题:很多子类具有相同的代码,那么重复的代码会变多,这样就造成了代码无法重用的问题,因为接口只是声明方法,而不能实现逻辑代码,这就是为什么不能复用代码的原因
这时一个设计原则就出来了:
找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
小明把Dcuk抽取的差不多了,呱呱叫(quck)和飞(fly)都抽取出来了,所以不需要对Duck类做什么处理了,现在为了要分开 变化和不会发生变化的部分,小明准备建立二组类,这些类完全远离Duck类,和Duck不会产生业务关系,一个是fly 飞相关的,一个是quack相关的,每一组类将实现各自的动作,比方说,我们可能有一个类实现 呱呱叫 ,另一个实现 吱吱叫 还有一个类实现 安静
我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变,为了要把这二个行为从Duck类中分开,我们将他们从Duck类中抽取出来,建立一组新类来代表每个行为,把Duck的行为定义成接口,然后在Duck中声明就可以
我们从图中可以看到鸭子的行为已经被隔离出来了,
小明还是感觉很开心的,感觉自己又对设计模式懂了很多,但是这些还不够,必须还要努力,毕竟还是没把最后的设计完美的体现出来,
现在的问题在于如何设计鸭子的行为?
鸭子的行为有呱呱叫和飞行的类,主要是要设计这二个类,而且必须是后期可维护性和可扩展性,不然后期需求一变,所以设计的必须要有弹性,前面设计的就是因为扩展性差导致后期需求变更满足不了,要大量的改代码,这肯定是不行的,这时候第二个设计原则出来了.
针对接口编程,而不是针对实现编程
package com.strategyparrent; import android.util.Log; /** * Created by admin on 2017/1/24. */ public class RedHeadDuck extends Duck implements Quackable { @Override public void display() { Log.e("Duck","红色的鸭子"); } @Override public void quack() { Log.e("Duck","鸭子呱呱叫"); } }上面我们是这样的代码,这样代码缺点就是太依赖实现,所以实现就是在quack()方法中的代码,如果哪天说quack的逻辑发生了更变,那么这个RedHeadDuck类中的quack()里面的代码也要去修改,如果能做到这个RedHeadDuck中的quack()方法发生了改变,我不需要改RedHeadDuck中的quack()代码,而是在其他的类中改就行,这样还能保证一个类只负责一个单一的功能,这也是设计原则之一,比如这样
package com.strategyparrent; import android.util.Log; /** * Created by admin on 2017/1/24. */ public class RedHeadDuck extends Duck { Quackable quackable; @Override public void display() { Log.e("Duck","红色的鸭子"); } @Override public void quack() { quackable.quack(); } }这样的要修改RedHeadDuck类中的quack()逻辑是不是只要修改Quackable类中的代码就行了,跟RedHeadDuck类没啥事,但是Quackable是一个接口,接口能调用方法呢?肯定要有实现类啊,对吧,这个时候设计有前进了一步,是这么设计的
我们看到FlyBehaving是一个接口,表示飞行的接口,下面二个子类,一个是能飞行的鸭子(FlyWithWings),一个是不能飞行的鸭子(FlyNoWay),所以只要继承FlyWithWings和FlyNoWay这二个类就能代表你这个鸭子是否具备飞行的功能,这样的做法就是类文件多了,但是扩展性提高了.这样我们具体的实现就不会和鸭子的子类有啥关系了,在子类中只有引用行为的具体类,然后去调用具体的行为方法就行。这样是不是被接口隔离开了么,还有一个好处就是我在Duck中声明的变量FlyWithWings 类型的变量,但是不知道我具体的子类是那个,这样不就是多态的概念么,而且你继承了FlyWithWings类就可以使用父类中的fly()方法,这就达到了复用代码的好处,除非你要重写fly()方法,而且这个时候已经和Duck(鸭子)没任何关系了,这和我们第一次使用到继承是有差别的,第一次使用继承你会带来所有的不具体飞行行为的鸭子具备了飞行的行为,而现在你不具体飞行行为直接继承FlyNoWay就可以了,
这就是把飞行和叫的接口抽取出来,然后让其各种行为去实现它,比如
飞行包括有的鸭子不能飞有的鸭子能飞,那么飞行就包括2个动作了,那么就有2个类去实现FlyBehavior接口,然后遇到一个不会飞的鸭子就继承不会飞的鸭子子类就ok,所以这样万一后期有个需求说让某个鸭子不能飞,这样的需求就很好做了,
package com.strategyparrent; import android.util.Log; /** * Created by admin on 2017/1/24. */ public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public void swim(){ Log.e("Duck","鸭子游泳"); } public abstract void display(); public void performQuack(){ quackBehavior.quack(); } public void performFly(){ flyBehavior.fly(); } }上面的performFly()就是鸭子飞行的动作,和Duck没关系了,为什么没关系了,因为performFly()方法中的代码是flyBehavir.fly()是flyBehavior这个变量去调用fly()方法了,而是委派给flyBehaior变量的对象去调用,那么flyBehavior这个变量怎么赋值的呢?不然不是空指针异常么,对吧,
package com.strategyparrent; import android.util.Log; /** * Created by admin on 2017/1/24. */ public class MallardDuck extends Duck { public MallardDuck(){ flyBehavior = new FlyWithWings(); quackBehavior = new Quack(); } @Override public void display() { Log.e("Duck","白色的鸭子"); } }子类去继承了Duck类后在构造函数中对其变量进行了初始化了,
上层调用:
package com.strategyparrent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Duck duck = new MallardDuck();//多态 duck.performFly(); duck.performQuack(); } }我们现在百度看下策略模式的定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化
在这个例子中算法是:鸭子的飞行行为和鸭子叫的行为,上面的一句话将每一个算法封装起来,在我们的例子中体现如图:
这是鸭子的飞行算法封装起来了,
这是鸭子叫的算法也封装起来了.
下面那句话算法独立于使用它的客户而独立变化,这话用图表示是这样的:
谁去new一个Duck子类去调用Duck中的方法谁就是客户或者调用者,比如我算法里面实现逻辑变了,客户知道么,肯定是不知道的,因为客户调用的是Duck类中的performQuack()方法,而这个方法具体的实现是在QuackBehavior具体的子类中.像在Duck类中使用FlyBehavior和QuackBehavior不是使用了继承,而是使用了组合,所以有来了一个设计原则
多用组合少用继承
OO基础:
抽象
封装
多态
继承
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
总算今天把这篇博客写完了,好累!