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

设计模式是进阶高级开发的必经之路。掌握设计模式,才能提升编码能力,设计出可复用、可扩展、可维护的软件系统。了解设计模式,才能更好理解开源类库的实现原理,解决问题。
策略模式(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方法来比较元素大小实现排序。

四、总结

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值