策略模式以及在Android中的应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18242391/article/details/81431060

《Android源码设计模式解析与实战》第7章策略模式笔记

介绍


在软件开发中,通常某一个功能可以有多种算法或者策略,我们需要根据不同的算法和策略完成该功能。针对这种情况,一种常规的方法就是将多种模式写在一个类中。然后通过if-else等条件判断语句来选择具体的算法。当多个算法集中在一个类中时,这个类就会变得臃肿,如果我们需要新增加一种策略,就需要修改这个类的源码,这就违反了OCP原则和单一职责原则。

如果将这些策略和算法抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,然后在客户端通过动态注入不同的策略和算法加以替换,这种模式的可扩展性、可维护性也就更高,也就是本篇文章要说的策略模式

使用场景


  • 1 .针对同一类型的不同处理方式,仅仅是处理方式有所差异。

  • 2 .出现同一个抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体的子类

UML类图

这里写图片描述

角色介绍

  • Context– 用来操作策略的上下文环境

  • Stragety – 策略的抽象接口

  • StrageyA, StargeyB – 具体的策略实现类


简单实现

我们以书上的公交车为例,用策略模式的实现

首先定义一个策略的抽象接口

/**
 *  计算的接口
 */
interface CalculateStrategy{
   fun calculatePrice(km:Int):Int
}

然后定义一个用来操作策略的上下文环境

class TranficCalculator{

    lateinit var mStrategy: CalculateStrategy

    fun setStrategy(strategy: CalculateStrategy):TranficCalculator{
        mStrategy = strategy
        return this
    }

    fun calculatePrice(km: Int): Int{
        return mStrategy.calculatePrice(km)
    }

}

接着定义不同的策略公交车和地铁票价

/**
 *  公交车价格计算的策略
 */
class BusStrategy : CalculateStrategy {
    override fun calculatePrice(km: Int): Int {
        //超过10公里的总距离
        val extraTotal = km - 10
        //超过的距离是5公里的倍数
        val extraFactor = extraTotal / 5
        //超过的距离对5公里取余
        val fraction = extraTotal % 5
        //价格计算
        var price = 1 + extraFactor * 1

        return if(fraction > 0) ++price; else price
    }
}

/**
 * 地铁价格计算的策略
 */
class SubwayStrategy : CalculateStrategy{
    override fun calculatePrice(km: Int): Int {

        return when {
            km <= 6 -> 3
            km in 7..11 -> 4
            km in 12..21 -> 5
            km in 22..31 -> 6
            else -> 7
        }

    }
}

剩下的就可以测试实现了

class StrategyPatternUnitTest {

    @Test
    fun demo1(){
        println("公交车16公里的价格:"+TranficCalculator().setStrategy(BusStrategy()).calculatePrice(16))
        println("地铁16公里的价格:"+TranficCalculator().setStrategy(SubwayStrategy()).calculatePrice(16))
    }

}

打印结果:
公交车16公里的价格:3
地铁16公里的价格:5

UML类图
这里写图片描述

这样就可以看到,通过策略模式简化了类的结构,方便了程序的扩展性、和解耦性,当我们在定义一个新的策略时候,只需要通过setStrategy 就可以轻松实现策略的替换,而不是用if-else 来做条件判断。这样保证了系统的简化逻辑以及接口,方便系统的可读性,对于业务的复杂逻辑也显得更加直观。


Android源码中的策略模式


经常使用动画的人应该都使用过插值器,对于想要控制动画的速度,让它加速或者减速运动,就可以通过插值器实现。对于插值器 就是策略模式的典型应用。它是一个接口,定义如下

public interface TimeInterpolator {
    float getInterpolation(float input);
}

Android系统提供这一个接口,外部只需要实现它,就可以实现用户自定义的插值器,想要看下在Android系统中的使用,就需要从外部调用处着手,当执行某个View动画时候,通过调用startAnimation 来启动动画

public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    //将动画设置到view中
    setAnimation(animation);
    invalidateParentCaches();
    //刷新view以及本身
    invalidate(true);
}

调用invalidate 刷新view视图的时候,会调用一个参数的draw方法刷新视图,接着会在方法里面调用dispatchDraw 方法, 在view中这个方法是一个空实现,看它的实现子类ViewGroup,会向它请求刷新视图,随后在这个方法里面会调用drawChild 刷新视图,看下这个方法的实现

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

它会转发调用View的三个参数的draw方法,在这个方法中会调用Animation

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

...
        //查看是否需要清除动画信息
        final int parentFlags = parent.mGroupFlags;
...
        //获取设置的动画信息
        final Animation a = getAnimation();
        if (a != null) {
            //绘制动画
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
        } else {
... 
        }
... 
    }

可以看到通过调用applyLegacyAnimation来应用动画,看下它的实现

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
...
    //是否已经初始化过动画
    final boolean initialized = a.isInitialized();
    if (!initialized) {
...
        //如果设置了动画的监听,则触发对应的回调       
        onAnimationStart();
    }
    //获取Transformation对象,存储动画的信息
    final Transformation t = parent.getChildTransformation();
    //调用了Animation的getTransformation,通过计算获取动画的相关值
    boolean more = a.getTransformation(drawingTime, t, 1f);
...
}

动画的最终实现调用AnimationgetTransformation 方法

public boolean getTransformation(long currentTime, Transformation outTransformation,
          float scale) {
      mScaleFactor = scale;
      return getTransformation(currentTime, outTransformation);
  }

这个方法主要是获取动画的缩放系数,并且调用Animation.getTransformation(currentTime, outTransformation) 来计算和应用动画效果。

public boolean getTransformation(long currentTime, Transformation outTransformation) {
...
    //计算时间流逝的百分比
    float normalizedTime;
    if (duration != 0) {
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }
    //动画是否已经完成
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    mMore = !expired;

...

    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
        //通过插值器来获取动画执行的百分比 看到策略模式的影子
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);  //1
        //根据获取的动画执行的百分比,来应用动画效果
        applyTransformation(interpolatedTime, outTransformation); //2
    }

    return mMore;
}

在注释1.处调用mInterpolator.getInterpolation()来获取设置的插值器

Android系统默认给我们提供了几种插值器,比如线性插值器LinearInterpolator

public class LinearInterpolator implements Interpolator{
    public float getInterpolation(float input) {
        return input;
    }
}

输入多少就输出多少

加速插值器AccelerateInterpolator

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

    public float getInterpolation(float input) {
        //mFactor默认是1.0f。
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }
}

输出是输入的平方,所以变化范围会越来越大

调用了getInterpolation 之后,会继续调用动画类的applyTransformation 来将属性值用到View上。这个方法在Animation 基类中是个空实现,选择ScaleAnimation来看下其具体实现

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float sx = 1.0f;
    float sy = 1.0f;
    float scale = getScaleFactor();
    //通过动画百分比来计算当时目标值
    if (mFromX != 1.0f || mToX != 1.0f) {
        sx = mFromX + ((mToX - mFromX) * interpolatedTime);
    }
    if (mFromY != 1.0f || mToY != 1.0f) {
        sy = mFromY + ((mToY - mFromY) * interpolatedTime);
    }
    //通过Matrix实现View的缩放
    if (mPivotX == 0 && mPivotY == 0) {
        t.getMatrix().setScale(sx, sy);
    } else {
        t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
    }
}

这个方法执行完,View的属性就会发生变化,然后不断重复,就产生了动画。

在这个过程中,插值器就扮演了一个策略模式的作用,当在一个系统中,提供一个统一的抽象的功能或接口,给系统使用,外部只要具体实现不同功能,就可以有不同的策略或算法实现,这就是策略模式的原理。

参考

《Android源码设计模式解析与实战》

猜你喜欢

转载自blog.csdn.net/qq_18242391/article/details/81431060