一、概述
一般问题:有时候一个系统需要动态地在几种算法中选择一种,或者一个对象需要动态地在几种行为中切换,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。
核心方案:将这些算法或行为封装成一个一个的类,使它们之间可以任意地替换。
设计意图:策略模式的设计核心是把对算法的调用责任和算法本身分割开来。我们把一个算法封装之后称之为一个策略,调用者只关注调用逻辑,而不关心策略主体,这就要求策略之间可以任意替换。那么定义一个接口来规范和统一各个策略就再合适不过,更何况这些策略本身的功能就是相似的。这样,调用者只需要保留一个接口对象即可,而不需要再保留所有策略实例。策略模式的设计图如下:
二、应用场景
Android锁屏密码的架构就采用了策略模式,解锁方式有简单密码解锁、图案密码解锁和复杂密码解锁等,每一种解锁方式都有各自的出场动画、消失动画、信息提示等。采用策略模式就是把每一种解锁方式设计成一个策略,为了方便讲解,我们做了简化和改动,设计图如下:
其中,KeyguardSecurityView是定义的策略接口
public interface KeyguardSecurityView { /** * Show a message on the security view with a specified color */ void showMessage(CharSequence message, int color); /** * Starts the animation which should run when the security view appears. */ void startAppearAnimation(); /** * Starts the animation which should run when the security view disappears. */ boolean startDisappearAnimation(Runnable finishRunnable); }
KeyguardPinView、KeyguardPatternView和KeyguardPasswordView分别对应简单密码、图案解锁和复杂密码三种解锁方式。以KeyguardPatternView代码为例,每一种解锁方式都有各自不同的方法体:
public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView{ /** * 图案解锁自己的信息展示方法实现 */ @Override public void showMessage(CharSequence message, int color) { if (!mLockPatternView.isEnabled()) { return; } mSecurityMessageDisplay.setNextMessageColor(color); mSecurityMessageDisplay.setMessage(message); } /** * 图案解锁自己的出场动画方法实现 */ @Override public void startAppearAnimation() { enableClipping(false); setAlpha(1f); setTranslationY(mAppearAnimationUtils.getStartTranslation()); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, 0, mAppearAnimationUtils.getInterpolator()); mAppearAnimationUtils.startAnimation2d( mLockPatternView.getCellStates(), new Runnable() { @Override public void run() { enableClipping(true); } }, this); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, mAppearAnimationUtils.getStartTranslation(), true /* appearing */, mAppearAnimationUtils.getInterpolator(), null /* finishRunnable */); } } /** * 图案解锁自己的消失动画方法实现 */ @Override public boolean startDisappearAnimation(final Runnable finishRunnable) { float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition() ? DISAPPEAR_MULTIPLIER_LOCKED : 1f; mLockPatternView.clearPattern(); enableClipping(false); setTranslationY(0); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, (long) (300 * durationMultiplier), -mDisappearAnimationUtils.getStartTranslation(), mDisappearAnimationUtils.getInterpolator()); DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor .needsSlowUnlockTransition() ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), () -> { enableClipping(true); if (finishRunnable != null) { finishRunnable.run(); } }, KeyguardPatternView.this); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, (long) (200 * durationMultiplier), - mDisappearAnimationUtils.getStartTranslation() * 3, false /* appearing */, mDisappearAnimationUtils.getInterpolator(), null /* finishRunnable */); } return true; } }
KeyguardBouncer是调用者,为方便展示,做了些改动
public class KeyguardBouncer{ //定义解锁方式,即策略变量 private KeyguardSecurityView mSecurityView; public void show() { SecurityMode securityMode = mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()); mSecurityView = getSecurityView(securityMode); //这里为解锁方式赋值 if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) { mKeyguardView.startAppearAnimation(); //调用解锁方式的出场动画方法,而不关心具体是哪种解锁方式 } } public void showMessage(String message, int color) { if (mKeyguardView != null) { mKeyguardView.showMessage(message, color); //调用解锁方式的信息展示方法,而不关心具体是哪种解锁方式 } else { Log.w(TAG, "Trying to show message on empty bouncer"); } } public void startPreHideAnimation(Runnable runnable) { mIsAnimatingAway = true; if (mKeyguardView != null) { mKeyguardView.startDisappearAnimation(runnable); //调用解锁方式的退场动画方法,而不关心具体是哪种解锁方式 } else if (runnable != null) { runnable.run(); } } }
分析上面代码:
- KeyguardSecurityView是对解锁方式的抽象,即策略模式的策略接口
- KeyguardPinView、KeyguardPasswordView和KeyguardPatternView是三种具体策略
- KeyguardBouncer是调用者,维护一个KeyguardSecurity变量,在适当时候直接调用变量方法,而不关心具体是哪种解锁方式
三、总结
优点:
- 各算法可以自由切换
- 避免多重条件判断
- 扩展性良好
缺点:
- 策略类可能会很多,造成类膨胀
- 原算法可以私有,但现在所有策略类都对外暴露
总结:策略模式是一种行为型的设计模式,可以优雅地避免多重条件选择语句而实现多种算法的自由切换。当策略多于四个时,应当考虑采用复合模式,从而避免策略类膨胀。
用一句话表述策略模式:
生旦净末丑,上台就是唱