Head First 设计模式学习——策略模式

来源:互联网 发布:文渊阁四库全书数据库 编辑:程序博客网 时间:2024/05/20 13:38

设计模式是进阶高级开发的必经之路。掌握设计模式,才能提升编码能力,设计出可复用、可扩展、可维护的软件系统。了解设计模式,才能更好理解开源类库的实现原理,解决问题。
策略模式(Strategy Pattern)是《Head First 设计模式》介绍的第一个模式。本文将做介绍,并介绍此模式在JDK中的应用。

一、定义

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,独立于使用算法的客户。

二、举例

背景:一个游戏中有各种各样的鸭子模型。有不同的外观,会游泳、呱呱叫,用java实现。

稍微有面向对象编程(OOP)经验的程序猿都会想到,应该有个鸭子超类,抽象鸭子的共同属性、行为。其他的鸭子具体实现。如

v1.0//鸭子超类public abstract class Duck {    //鸭子类型    private String duckName="Duck";    public Duck(String duckName) {        this.duckName = duckName;    }    //不同鸭子外观不同,所以由子类实现    public abstract void display();    //所有鸭子都会呱呱叫,由父类实现    public void quack(){        System.out.println(duckName+":会呱呱叫");    }    //所有鸭子都会游泳,由父类实现    public void swim(){        System.out.println(duckName+":会游泳");    }}

此时有GreenDuck:

class GreenDuck extends Duck{    public GreenDuck(String duckName) {        super(duckName);    }    //具有不同外观的绿鸭实现display()方法    @Override    public void display() {        System.out.println(duckName+":是绿色的");    }}

此外,还有RedDuck,BlueDuck等各种鸭子。他们都继承Duck类,实现自己的外观,在游戏里游泳、发出呱呱叫的声音,一切都很和谐,玩家也很喜欢:

    public static void main(String[] args) {        GreenDuck greenDuck = new GreenDuck("green duck");        greenDuck.swim();        greenDuck.quack();        greenDuck.display();        RedDuck rednDuck = new RedDuck("red duck");        rednDuck.swim();        rednDuck.quack();        rednDuck.display();    }控制台打印:green duck:会游泳green duck:会呱呱叫green duck:是绿色的red duck:会游泳red duck:会呱呱叫red duck:是红色的

唯一不变的是变化,好景不长,竞品公司的鸭子类型比我们多,不仅有红蓝绿鸭,还有模型鸭,木头鸭甚至黄金鸭,功能比我们强大,有些会飞,有些能发出“叽叽”声,有些能喷火,有些能射出子弹,抢了不少市场份额。我们需要迎头赶上。
变化:有些鸭子会飞,有些不会,木头鸭不会叫,模型鸭会叽叽叫,有些是呱呱叫。

方案1、Duck父类中加入fly()方法,会飞的继承,不会飞的覆盖,如fly(){//什么都不做}。弊端:以后每新增一个子类,都要覆盖fly()方法。quack()方法也一样。

方案2、将fly()、quack()方法抽象成Flyable、Quackable接口。会飞的鸭子实现Flyable接口并实现fly()方法,Quackable同样处理。乍一看,用接口处理确实提高的灵活性。想要什么功能就实现什么接口。但也造成另外一个灾难:若以后fly()的逻辑要更改,比如从直线飞行变成曲线飞行。实现接口的子类多少决定了你加班的时长。而且针对fly()这个动作子类中应该会有大量的重复代码。

要想解决此问题,需要引出一个设计原则:找出应用可能变化之处,把他们独立出来,不要和那些不需要变化的混在一起

此案例中。鸭子的基本属性不变,行为发生改变。所以,我们应该把行为,从鸭子类中独立出来。

此处有fly()和quack()行为有变化(其他的类似,此处仅以这两个举例),我们可以把其提取到一个类中。不过为了进一步分离,我们甚至可以分离到两个类中:一个Fly类(或接口)和一个Quack类(或接口):

//叫 接口public interface Quack {    void quack();}//不同的叫声可以有不同的实现class QuackBehaviorGua implements Quack{    @Override    public void quack() {        System.out.println("我是这样叫的:呱呱呱");    }}class QuackBehaviorGi implements Quack{    @Override    public void quack() {        System.out.println("我是这样叫的:叽叽叽");    }}//飞  接口同样如此 (略)//这样设计,能让飞、叫这样的行为跟鸭子分离开。若此时有别的模型,鸡、牛等,也可以使用这些代码,达到复用。而且新增行为不会影响到原来的代码。厉害了我的哥

此时只需要把鸭子和行为关联起来,即在鸭子的内部,需要能够调用fly()或者quack()方法。不过首先,我们需要能够设置鸭子的行为,重构鸭子代码:

//鸭子超类public abstract class Duck {    //鸭子类型    String duckName="Duck";    //负责处理不同行为,需要持有这些委托的引用,而且是接口类型    Fly flyBehavior;    Quack quackBehavior;    //构造器需要传入行为类(可以通过构造器参数传入,也可以在子类其他构造方法中实现)    public Duck(String duckName,Fly fly,Quack quack) {        this.duckName = duckName;        this.flyBehavior = fly;        this.quackBehavior = quack;    }    //不用管具体怎么飞,只要能飞就行    public void CommonFly(){        flyBehavior.fly();    }    //不用管具体怎么叫,只要能叫就行    public void CommonQuack(){        quackBehavior.quack();    }}

此处引出另一个设计原则:针对接口编程,而不是针对实现编程

此例中飞、叫的行为,在客户端(Duck)中不用关心具体的实现是什么,只需知道有这么一个接口能实现需要的功能。直线或曲线、呱呱或叽叽在运行时确定而不是编译器(即代码没有写死)。

至此,我们需要在创造新的鸭子时,指定它拥有的行为是怎样的,直线飞:则实现一个直线飞的行为类传递到构造方法,曲线飞、呱呱叫、叽叽叽叫同样如此:

    public static void main(String[] args) {        String name = "直线飞、呱呱叫鸭";        //直线飞行        Fly flybehavior = new FlyBehaviorZhi();        //呱呱叫        Quack quackbehavior = new QuackBehaviorGua();        GreenDuck greenDuck ;        greenDuck = new greenDuck(name,flybehavior,quackbehavior);        greenDuck.display();        greenDuck.CommonFly();        greenDuck.CommonQuack();        System.out.println("-------------------");        name = "曲线飞、叽叽叫鸭";        flybehavior = new FlyBehaviorQu();        quackbehavior = new QuackBehaviorGi();        RedDuck redDuck = new RedDuck(name,flybehavior,quackbehavior);        redDuck.display();        redDuck.CommonFly();        redDuck.CommonQuack();    }控制台输出:我是:直线飞、呱呱叫鸭我是这样飞的:直线我是这样叫的:呱呱呱-------------------我是:曲线飞、叽叽叫鸭我是这样飞的:曲线我是这样叫的:叽叽叽

至此,我们成功分离了行为和鸭子类。极大提高程序扩展性和复用性。

三、策略模式在JDK中的应用

学习设计模式最好的方式之一是分析其他类库中的使用案例,而JDK是最好的选择,因为你不仅可以了解设计模式,还能更深入的理解JDK。

JDK中应用策略模式的有:
● java.util.Comparator#compare()
● javax.servlet.http.HttpServlet
● javax.servlet.Filter#doFilter()

//JDK1.2中加入的java.util.Comparator的源码public interface Comparator<T> {//包含两个抽象方法    int compare(T o1, T o2);    boolean equals(Object obj);}

使用场景之一是集合的排序中算法中。
java.util.Collections.sort(java.util.List, java.util.Comparator)API中可以传入一个Comparator实现compare(T o1, T o2)方法的实现类,在最底层的调用中调用compare方法来比较元素大小实现排序。

四、总结

策略模式定义一组算法,并把其封装到一个对象中。然后在运行时,可以灵活的使用其中的一个算法。对于调用算法的客户端来说,不用关心最终是什么样的算法来运行。只需要知道运行了一个满足需求的算法即可。因为算法做了封装,且与客户端分离,可以做到复用。

原创粉丝点击