《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);
...
}
动画的最终实现调用Animation
的getTransformation
方法
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源码设计模式解析与实战》