Android设计模式——策略模式之源码使用场景(三)

1,683 阅读7分钟

一、前言

策略模式可以定义一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

二、策略模式的使用场景

  • 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
  • 需要安全地封装多种同一类型的操作时。
  • 出现同一个抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时。

三、策略模式的实现

通常一个问题有多个解决方案时,最简单的方式就是利用if-else或者switch-case方式根据不同的情景选择不同的解决方案,但这种简单的方案问题太多,例如耦合性太高、代码臃肿、难维护等。但是如果解决方案中包括大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,当需要增加一种方案时就需要修改类中的代码。

使用if-else这种方法确实不会遵循开闭原则,而应对这种情况策略模式就能很好地解决这类问题,它将各种方案分离开来,让程序客户端根据具体的需求来动态地选择不同的策略方案。

下面我们通过交通工具来进行费用计算演示,对于部分城市的公交都是实行分时段计价,坐得越远费用越高。显然公交与地铁的价格计算是不一样的,我们算一下乘坐不同出行工具的成本,代码:

public class PriceCalculator {

    private static final int BUS = 1;
    private static final int SUBWAY = 2;

    public static void main(String[] strings) {
        PriceCalculator calculator = new PriceCalculator();
        System.out.println("做20km公交票价:" + calculator.calculatePrice(20, BUS));
        System.out.println("做20km地铁票价:" + calculator.calculatePrice(20, SUBWAY));

    }


    /**
     * 公交车计价    10公里之内1块钱,超过10公里每加一块可以乘5公里
     *
     * @param km
     * @return
     */
    private int busPrice(int km) {
        //超过10公里的距离
        int extraTotal = km - 10;
        //超过距离是5km的倍数
        int extraFactor = extraTotal / 5;
        //超过的距离对5km取余
        int fraction = extraTotal % 5;
        //价格计算
        int price = 1 + extraFactor % 5;
        return fraction > 0 ? ++price : price;
    }

    /**
     * 6km内3块,6—12km是4块,12—22km是5块,22-32km是6块,其他距离7块
     *
     * @param km
     * @return
     */
    public int subwayPrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        return 7;
    }

    /**
     * 根据不同类型计算
     *
     * @param km
     * @param type
     * @return
     */
    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        }
        return 0;
    }

}

PriceCalculator 类很明显的问题就是并不是单一职责,首先它承担计算公交车和地铁乘坐价格的职责,另一个问题就是通过if-else的形式来判断使用哪种计算形式。当我们增加一种出行方式时,如出租车,那么我们就需要在PriceCalculator 中增加一个方法来计算出租车出行的价格,并且在calculatePrice(int km, int type)函数增加一个判断,代码:

public class PriceCalculator {

    
    private static final int TAXI = 3;

    /***
     * 每公里2块
     *
     * @param km
     * @return
     */
    private int taixPrice(int km) {
        return km * 2;
    }

    /**
     * 根据不同类型计算
     *
     * @param km
     * @param type
     * @return
     */
    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        } else if (type == TAXI) {
            return taixPrice(km);
        }
        return 0;
    }

}

此时代码已经很混乱,各种if-else语句缠绕在一起。当价格的计算方法变化时,需要直接修改这个类中的代码,那么很可能有一段代码是其他几个计算方法所公共使用的,这就容易引入错误。另外增加出行方式时,我们又需要在calculatePrice中添加if-else,此时很有可能就是复制一个if-else,然后手动进行修改,手动复制代码也是最容易引入错误的做法之一。这类代码必然是难以应对变化的,它会使得代码变得越来越臃肿,难以维护,我们解决这类问题的手法也就是使用策略模式。我们也可以把每种计算方式独立成一个函数,然后外部调用对应的方法即可,但这也是另一种耦合的形式,对于可变性较大的算法来说还是不适合使用这种方式。

接下来使用策略模式对以上进行重构。首先定义一个抽象的价格计算接口:

//计算接口
public interface CalculateStrategy {
    //按距离来计算价格
    int calculatePrice(int km);
}

对于每一种出行方式我们都有一个独立的计算策略类,这些策略类都实现了CalculateStrategy 接口,下面是公交车与地铁的计算策略类:

public class BusStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        int extraTotal = km - 10;
        int extraFactor = extraTotal / 5;
        int fraction = extraTotal % 5;
        int price = 1 + extraTotal % 5;
        return fraction > 0 ? ++price : price;
    }
}
public class SubwayStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        return 7;
    }
}

我们再创建一个扮演Context角色的类,这里将命名为TranficCalculator,代码:

public class TranficCalculator {

    private CalculateStrategy strategy;

    public void setStrategy(CalculateStrategy strategy) {
        this.strategy = strategy;
    }

    public int calclatePrice(int km) {
        return strategy.calculatePrice(km);
    }

    public static void main(String[] strings) {
        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new BusStrategy());
        System.out.println("公交车20km价格:" + calculator.calclatePrice(20));
    }

}

这种方式在隐藏实现的同时,可扩展性变得很强,当我们需要添加出租车的计算策略时,只需要添加一个出租车计算策略类,然后将该策略设置给TranficCalculator ,最好直接通过TranficCalculator 对象的计算方法即可。代码:

public class TaxiStratrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        return km * 2;
    }
}

将策略注入TranficCalculator中。

public class TranficCalculator {

    private CalculateStrategy strategy;

    public void setStrategy(CalculateStrategy strategy) {
        this.strategy = strategy;
    }

    public int calclatePrice(int km) {
        return strategy.calculatePrice(km);
    }

    public static void main(String[] strings) {
        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new TaxiStratrategy());
        System.out.println("出租车20km价格:" + calculator.calclatePrice(20));
    }

}

通过以上清晰的看出二者的区别。前者通过if-else来解决问题,实现简单,层级单一,暴露问题多,代码臃肿,逻辑复杂,难以升级与维护,没有结构可言;后者通过建立抽象,不同的策略建成一个具体的策略实现,通过不同的策略实现算法替换。简化逻辑、结构的同时,增强系统的可读性、稳定性、可扩展性,对于较复杂的逻辑显得更为直观,扩展更为方便。

四、Android源码中使用的策略模式

在动画执行过程中,我们需要一些动态效果,有点类似电影的慢镜头,有时需要慢一点,有时需要快一点,这样动画变得灵动起来,这些动态效果就是通过插值器(TimeInterpolator)实现的,我们只需要对Animation对象设置不同的插值器就可以实现不同的动态效果,该效果则使用的就是策略模式实现。

  • AccelerateDecelerateInterpolator 在动画开始与介绍的地方速率改变比较慢,在中间的时候加速
  • AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速
  • AnticipateInterpolator 开始的时候向后然后向前甩
  • AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
  • BounceInterpolator 动画结束的时候弹起
  • CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
  • DecelerateInterpolator 在动画开始的地方快然后慢
  • LinearInterpolator 以常量速率改变
  • OvershootInterpolator 向前甩一定值后再回到原来位置
  • PathInterpolator 路径插值器