1.定义
策略模式定义了一系列算法,并将每一个算法封装起来,而且使他们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
这种定义的语句总是有一种通病,喜欢把一个简单事物复杂化,这里举个例子来说明一下策略模式到底是用来干啥的。
比如我用代码来解一道数学题,然后由于本人是一个天才,牛逼到不行的那种,我可以用N种解法来求解。每种解法都是一种解决方案,但每次使用时,需要根据外在的约束条件来决定到底使用哪种算法来求解,原来的方法大概是来一个switch,搞很多case,来决定到底用哪个方案。这种方式一旦要修改就比较麻烦了,现在可以用策略模式来处理这种问题了。
看图说话
附上UML图一张:
图片来源与网络
分析一下这个图,在contect中有三种算法A,B,C可以解决,于是将这三种算法独立出去成为单独的类,他们有一个共同的父类,而Context类不需要与具体的算法类接触。
还是看看代码具体的实现
光看这个图还是比较模糊的。
先看一下算法的父类:
//算法的抽象父类
public abstract class StrategyAbs {
//每一个算法中都应该有的方法
abstract boolean DoWork();
}
所有的算法的具体实现不同,但是他们最终实现的功能或效果应该是相同的,因此讲道理是可以提取出一个或多个方法的。
然后是算法的具体实现:
//算法
public class AStrategy extends StrategyAbs {
private static final String TAG = "Strategy";
@Override
void DoWork() {
Log.d(TAG, "DoWork: i am A,i am working now");
}
}
这里假设有A,B,C三种算法,都是一样的,这里只列出A的代码。
StrategyUtil:
class StrategyUtil {
private StrategyAbs strategyAbs;
//调用者在这里传进来的就是调用者想要使用的A,还是B还是C
//传进来的都是StrategyAbs的子类,所以用StrategyAbs可以统一接收
public void setStrategyType(StrategyAbs strategyType) {
strategyAbs = strategyType;
}
//调用父类的方法,而父类的方法是抽象的,所以会自动执行相应子类的方法
public void work() {
strategyAbs.DoWork();
}
}
调用者只会和这个类接触,调用者因此不用关心具体算法类是怎么实现的。
最后看到底怎么去调用:
xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="st.zlei.com.strategypattern.MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/a_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="do work By A!"
/>
<Button
android:id="@+id/b_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="do work By B!"
/>
<Button
android:id="@+id/c_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="do work By C!"
/>
<Button
android:id="@+id/overAll_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="do work By C!"
/>
</LinearLayout>
xml布局很简单,就长这样:
实现的功能是点击不同的Button,就采用不同的策略。
调用者:
a_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取对象
StrategyUtil strategyUtil = new StrategyUtil();
//通过setStrategyType注入想要采用策略类型
strategyUtil.setStrategyType(new AStrategy());
//调用策略的方法
strategyUtil.work();
}
});
b_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StrategyUtil strategyUtil = new StrategyUtil();
strategyUtil.setStrategyType(new BStrategy());
strategyUtil.work();
}
});
c_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StrategyUtil strategyUtil = new StrategyUtil();
strategyUtil.setStrategyType(new CStrategy());
strategyUtil.work();
}
});
最后看一下打印结果:
可以看到点击A按钮,策略A就开始working,点击B按钮,策略B开始working,点击C按钮,策略C开始working。
还有一点就是这种写法还可以自定义一种策略:
a_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取对象
StrategyUtil strategyUtil = new StrategyUtil();
//通过setStrategyType注入想要采用策略类型
//当在这里不传子类策略的对象,而是直接new 一个父类的抽象类,就必须要重写DoWork方法,已达到自定义的功能
strategyUtil.setStrategyType(new StrategyAbs() {
@Override
boolean DoWork() {
return false;
}
});
//调用策略的方法
strategyUtil.work();
}
});
番外:
现在假设有一种需求,我希望优先使用A策略,其次使用B策略,当A,B策略都无法工作时,才采用C策略。
这时候xml代码多加一个button了,像这样:
<Button
android:id="@+id/overAll_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="do work First By A then B then C"
/>
修改一下A,B,C策略的方法:
public class AStrategy extends StrategyAbs {
private static final String TAG = "Strategy";
//当返回true的时候,表示A策略可以正常工作,false表示A策略无法工作
@Override
boolean DoWork() {
Random random = new Random();
boolean b = random.nextBoolean();
if (b){
Log.d(TAG, "DoWork: i am A,i am working now");
}else {
Log.d(TAG, "DoWork: i am A,i am busying");
}
return b;
}
}
public class BStrategy extends StrategyAbs {
private static final String TAG = "Strategy";
//当返回true的时候,表示B策略可以正常工作,false表示B策略无法工作
@Override
boolean DoWork() {
Random random = new Random();
boolean b = random.nextBoolean();
if (b){
Log.d(TAG, "DoWork: i am B,i am working now");
}else {
Log.d(TAG, "DoWork: i am B,i am busying");
}
return b;
}
}
public class CStrategy extends StrategyAbs {
private static final String TAG = "Strategy";
@Override
boolean DoWork() {
//c策略作为A,B,的备用策略,它必须要保证自己是可用的。
Log.d(TAG, "DoWork: i am C,i am working now");
return true;
}
}
添加一个策略
使用这个策略就可以按照需求顺序使用A,B,C策略
class OverALlStrategy extends StrategyAbs {
AStrategy aStrategy = new AStrategy();
BStrategy bStrategy = new BStrategy();
CStrategy cStrategy = new CStrategy();
@Override
boolean DoWork() {
if (!aStrategy.DoWork()) {
if (!bStrategy.DoWork()) {
cStrategy.DoWork();
}
}
return false;
}
}
调用者:
overAll_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StrategyUtil strategyUtil = new StrategyUtil();
strategyUtil.setStrategyType(new OverALlStrategy());
strategyUtil.work();
}
});
再看一下打印结果:
满足了优先使用A策略,再使用B策略,最后使用C策略的要求。
总结
到这里策略模式就讲完了,这种模式的可扩展性,可维护性更好,核心内容就在于setStrategyType这个方法,传入的是具体的策略子类,但是用抽象父类的引用去接收,接收的始终不变,可是传入的可以改变成不同的策略。因此要求所有子类的功能实现的方法是统一的,以便被抽象到父类中去。举个例子比如A,B,C策略的功能实现的方法都叫叫DoWork,父类才可以有抽象的方法DoWork,然后根据setStrategyType传入的具体策略的类型,会自动调用相应子类的DoWork方法,如果A策略的功能方法叫methodA,B策略的功能方法叫methodB,C策略的功能方法叫methodC。那么父类是无法调用的,因为父类是无法知道该调用哪个的。
这文使用的代码可以在本人github上找到-->传送门