03、策略模式--Strategy
![](https://img.haomeiwen.com/i1586207/3e53a7925ddfb32a.png)
版权声明:本文为博主原创文章,未经博主允许不得转载
PS:转载请注明出处
作者: TigerChain
地址: http://www.jianshu.com/p/135532803cdb
本文出自 TigerChain 简书 人人都会设计模式
教程简介
- 1、阅读对象
本篇教程适合新手阅读,老手直接略过 - 2、教程难度
初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢 - 3、Demo 地址
https://github.com/githubchen001/DesignPattern 请看 Strategy 部分
正文
一、什么是策略模式
1、 生活中的策略
比如说我要出行旅游,那么出行方式有--飞机、自驾游、火车等,这几种方式就是策略。再比如:某大型商场搞活动--满 100 元送杯子,满 300 减 50 ,满 1000 元抽奖「一等将彩色电视机」,这种活动也是策略。在游戏中,我们打一个普通的怪使用普通的招即可,打大 BOSS 就要是用大招,这也是一种策略 ...
2、程序中的策略
就是对各个算法的一个封装「不是实现算法,而是封装算法」,让客户端非常容易的调用,省掉了客户端 if else 恶心的判断,让客户端独立于各个策略
这里举一个简单的例子:比如我们在 Android 中一定会使用到 http 网络请求,请求库太多了,大概有 AsyncHttpclient,OkhttpClient,Volley 等「具体的策略」,那么我们完全可以使用一个策略模式,定义一个抽像策略,然后把各个请求策略封装,客户想使用哪个就使用哪个,非常灵活和方便
策略模式和简单工厂很相似,确有不同,策略是一种行为模式,而简单工厂是创建型模式「创建对象」 后面再说
策略模式的定义
策略是对算法的封装,是一种形为模式,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换
策略的特点
- 是一种行为模式,对算法封装,使得客户端独立于各个策略
- 扩展性强,添加策略无非就是添加一个具体的实现类而已,代价非常低
策略模式的结构
角色 | 类别 | 说明 |
---|---|---|
Strategy | 抽象的策略 | 是一个接口或抽象类 |
ConcreteStrategy | 具体的策略类 | 实现了抽象的策略 |
Context | 一个普通的类 | 上下文环境,持有 Stragegy 的引用 |
策略模式简单的 UML
![](https://img.haomeiwen.com/i1586207/29f591a9dced4a6a.jpg)
二、策略模式举例
1、曹操败走华荣道
我们知道三国演义中曹操败走华容道的故事,相传在赤壁之战之后,曹操的船舰被刘备烧了,曹操逃离时面前有两条路:1、平坦的大路。2、泥泞的华容道。面对这两条路,曹操没有选择大路而选择有炊烟的小路「华容道路」,理由---实则虚之,虚则实之,那么曹操在选择道路的时候其实就是选择策略
败走华容道的简单的 UML
![](https://img.haomeiwen.com/i1586207/e184ba88afb78dff.jpg)
根据 UML 编码
- 1、定义一个路的抽象策略
/**
* 抽象的策略,定义逃跑路线
*/
public interface IRunStrategy {
// 逃跑线路
void escapeRoute() ;
}
- 2、定义具体的路径--大路
/**
* 具体的策略一走大路
*/
public class Highroad implements IRunStrategy {
@Override
public void escapeRoute() {
System.out.println("走大路");
}
}
- 3、定义具体路线--华容道
/**
* 具体的策略二走华容道
*/
public class HuaRongRoad implements IRunStrategy {
@Override
public void escapeRoute() {
System.out.println("走华容道");
}
}
- 4、定义上下文,选择方式
/**
* 上下文 持有 IRunStrategy 的引用
*/
public class ContextRunStrategy {
private IRunStrategy iRunStrategy ;
public ContextRunStrategy(IRunStrategy iRunStrategy){
this.iRunStrategy = iRunStrategy ;
}
/**
* 选择道路
*/
public void choiceRoad(){
iRunStrategy.escapeRoute();
}
}
- 5、主角曹操登场,看看曹操是如何选择道路的
/**
* 曹操选择路线
*/
public class CaoCao {
public static void main(String args[]){
/**
* 曹操疑心重,选择了华容道,对曹操来说至于杂样走华容道,不关心,死多少人也不关心,只关心我要走这条道就好
*/
IRunStrategy huaRongRoad = new HuaRongRoad() ;
ContextRunStrategy contextRunStrategy = new ContextRunStrategy(huaRongRoad) ;
contextRunStrategy.choiceRoad();
}
}
真的走了华容道,好吧 no zuo no die ,我们可以看到上面曹操选择逃跑路线都是行为,所以很适合策略模式「策略模式就是一种选择模式,当你举棋不定的时候就使用策略模式」
注意: 策略的核心不是如何实现算法,而是如何更优雅的把这些算法组织起来,让客户端非常好调用「虽然策略非常多,可以自由切换,但是同一时间客户端只能调用一个策略,其实也很好理解,你不可能同时既坐飞机,又坐火车」。
2、出行旅行方式
经过上面的曹操败走华荣道,我们对策略有了感觉了吧,那么下来我们趁热打铁,再来一发,我们都知道出去旅行一般方式:坐飞机、坐火车、坐大巴、自驾游等等,这一个个的出行方式就是策略,接下来我给出简单的 UML 图,代码部分请各自自行实现「道理都懂,你的生活质量还是没有提高,方法再多也不见有多成功,就是因为实践太少,动手才是真理,靠--忘记吃药了,脉动回来」
出行方式简单的 UML
![](https://img.haomeiwen.com/i1586207/c95dfb868dce8733.jpg)
代码实现
大家根据出行的 UML 图实现代码即可「非常简单,相信都可以实现」
3、Android 中使用策略场景
段子来了
先看个段子,轻松一下「注以下只是一个简单举例,库不分先后,俗话说没有最好,只有最适合」
相信做 Android 的朋友都离不开网络请求,有一天你「小明」发现了一个传说中很好的网络请求库 AsyncHttpClient ,你高兴的把网络请求相关的 API 都实现了,经理看到了说不错不错,写的很快吗,突然有一天,经理说:小明 AsyncHttpClient 好多 API 过时了「随着 Android 版本的更新」,并且对 RestFul 支持的不太友好,我看到一个叫 Retorfit2「听说是最好的网络」 的库,默认支持 OkHttp ,用 Retorfit 把 AsyncHttpClient 替换了吧,非常简单对你来说,小明这时估计心里飘过了一千匹羊驼「我靠,为麻不早说」,又过了一些时间,经理又说,小明呀,Volley 是 Google 推荐的网络请求库,你换成 Volley 库吧,小明此时估计把经理的八辈祖宗都问候了一遍,又是一通加班加点的改,最后 Happy 的改好了。后面又有一个牛 B 的库,经理又让替换,小明哭了「为什么受伤的总是我」...
看到这里大家应该想到了,上面的请求场景就是一个个的策略,如果小明按照策略模式走下来,只是添加扩展子策略,压根原来的方法毛都不用改,只能说,小明呀,你可张点心吧。
MVP + 策略模式
下面我们使用 MVP + 策略模式模拟一个简单的登录功能,实现上面小明的需求
MVP+retorfit+rx 请求策略简单的 UML
![](https://img.haomeiwen.com/i1586207/462a41c012dbad5d.jpg)
根据 UML 撸码
首先我们要使用 AsyncHttpClient、Retorfit 等,先添加配置 Gradle「项目 Module 的 build.grade中」
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.5'
compile 'com.loopj.android:android-async-http:1.4.9'
注: 以下代码纯粹是为了演示策略模式,顺手写的,好多细节可能没有考虑到,但是基本框架就是这样的,可以自行完善
- 1、分别新建 MVP 的基类接口,IPresenter,Model,IView
/**
* @Description MVP 中的 Presenter 基
* @Creator TigerChain(创建者)
*/
public interface Presenter {
}
/**
* @Description MVP 中的 Model 基类
* @Creator TigerChain(创建者)
*/
public interface Model {
}
/**
* @Description MVP 中的 View 基类
* @Creator TigerChain(创建者)
*/
public interface IView {
}
- 2、新建 MVP 的关联接口
ILoginContact.java
「当然也可以不写此类,直接写登录 MVP 的直接子类」
package designpattern.tigerchain.com.mvphttpstrategy.mvp;
import designpattern.tigerchain.com.mvphttpstrategy.mvp.domain.User;
import io.reactivex.Observable;
/**
* @Description MVP 的关联类「也可以单独创建 MVP 就是有点乱」
* @Creator TigerChain(创建者)
*/
public interface ILoginContact {
interface LoginView extends IView{
//显示进度条
void showProgress() ;
//隐藏进度条
void hideProgress() ;
//登录成功
void loadSuccess(String str) ;
//登录失败
void loadFailed(String str) ;
//取得用户名
String getUserName() ;
//取得用户密码
String getUserPass() ;
//清除输入框
void clearEditText() ;
//用户名和密码不能为空
void editnotNull() ;
}
interface LoginPresenter extends Presenter{类
/**
* 登录功能
*/
void login() ;
/**
* 清除输入框架内容
*/
void clear() ;
}
interface ILoginModel extends Model{
/***
* 登录的方法,其实这里就是一个抽象策略,至于你使用 retrofit 还是 asynchttpClient 还是 Volley 那是自己的事情
* @param uName
* @param uPass
* @return
*/
Observable<User> login(String uName, String uPass) ;
}
}
其中 ILoginModel 就是一个抽象策略,这里是登录功能
- 3、分别实现具体的策略「使用不同的网络请求库调用登录 API」
具体策略1:使用 AsyncHttpClient 调用登录
/**
* @Description 具体策略使用 AsyncHttpClient 来调用登录 API
* @Creator TigerChain(创建者)
*/
public class AsynchHppClientImplLogimModel implements ILoginContact.ILoginModel {
@Override
public Observable<User> login(final String uName, final String uPass) {
return Observable.create(new ObservableOnSubscribe<User>() {
@Override
public void subscribe(final ObservableEmitter<User> e) throws Exception {
AsyncHttpClient client = new AsyncHttpClient() ;
// 这里就是一个请求 没有真正的对接服务器,只是一个演示
client.get("http://www.baidu.com", new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
if(uName.equalsIgnoreCase("TigerChain") && uPass.equals("123")){
User user = new User() ;
user.setuName(uName);
user.setUpass(uPass);
e.onNext(user);
e.onComplete();
}else{
e.onNext(null);
e.onComplete();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
e.onError(error);
}
}) ;
}
});
}
}
具体策略2:使用 Volley 调用登录 API
/**
* @Description 具体策略使用 Volley 实现登录功能
* @Creator TigerChain(创建者)
*/
public class VolleyImplLoginModel implements ILoginContact.ILoginModel {
@Override
public Observable<User> login(final String uName, final String uPass) {
return Observable.create(new ObservableOnSubscribe<User>() {
@Override
public void subscribe(final ObservableEmitter<User> e) throws Exception {
/***
* 这里调用和 Volley 相关的 API 实现登录即可
*/
}
});
}
}
具体策略3:使用 RetorFit 调用登录 API
/**
* @Description 具体策略 使用 RetorFit 实现登录功能性
* @Creator TigerChain(创建者)
*/
public class RetorFitImplLoginModel implements ILoginContact.ILoginModel {
@Override
public Observable<User> login(final String uName, final String uPass) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://")
.build();
ILoginRetorfitApi loginService = retrofit.create(ILoginRetorfitApi.class) ;
return loginService.login(uName,uPass) ;
}
}
其中 User 和 ILoginRetorfitApi 类分别是:
# User.java
/**
* @Description 普通人的 Java
* @Creator TigerChain(创建者)
*/
public class User {
private String uName ;
private String Upass ;
public String getuName() {
return uName;
}
public void setuName(String uName) {
this.uName = uName;
}
public String getUpass() {
return Upass;
}
public void setUpass(String upass) {
Upass = pass;
}
}
# ILoginRetorfitApi.java
/**
* @Description Retrofit API
* @Creator TigerChain(创建者)
*/
public interface ILoginRetorfitApi {
@GET("/login")
Observable<User> login( @Field("userName") String userName,
@Field("passWord")String passWord) ;
}
- 4、策略中的上下文「这里就是我们具体的 P」 LoginPresenterImpl.java
/**
* @Description MVP 中的P ,就相当于策略中Context
* @Creator junjun(创建者)
*/
public class LoginPresenterImpl implements ILoginContact.LoginPresenter {
private ILoginContact.ILoginModel iLoginModel ;
private ILoginContact.LoginView loginView ;
public LoginPresenterImpl(ILoginContact.LoginView loginView,ILoginContact.ILoginModel iLoginModel){
this.iLoginModel = iLoginModel ;
this.loginView = loginView ;
}
@Override
public void login() {
String uName = loginView.getUserName() ;
String uPass = loginView.getUserPass() ;
if(TextUtils.isEmpty(uName) || TextUtils.isEmpty(uPass)){
loginView.editnotNull();
return ;
}
loginView.showProgress();
iLoginModel.login(uName,uPass)
// subscribeOn(Schedulers.io()) 由于 AsyncHttpClient 本身就是在子线程去请求的,所以这里为了演示把这个去掉
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(User user) {
loginView.loadSuccess("登录成功");
}
@Override
public void onError(Throwable e) {
loginView.loadFailed("用户名或密码错误,登录失败");
loginView.hideProgress();
}
@Override
public void onComplete() {
loginView.hideProgress();
}
}) ;
}
@Override
public void clear() {
loginView.clearEditText();
}
}
到此为止,我们的 MVP+RX+Retorfit 带策略的登录功能就完成了。
- 5、客户端调用「在 Activity 中调用」
下面来看客户调用,不贴代码了「放一张部分代码截图」,后面放出全部 DEMO 大家自行查看
![](https://img.haomeiwen.com/i1586207/f9f4faf3b077098e.png)
怎么样,通过以上几个例子,相信我们对策略模式有了很好的理解了
- 6、最后运行看一下
![](https://img.haomeiwen.com/i1586207/4a5a96a135000a1f.gif)
demo 没有实现完毕,其中 Retorfit 和 Volley 没有完善,有兴趣的可以自行完善
Demo 地址:https://github.com/githubchen001/mvp-rx-loginStrategy
三、Android 源码中的策略模式
1、TimeInterpolator 时间插值器
做过动画的朋友都知道,插值器的概念,一句话就是:设置不同的插值器,动画可以以不同的速度模型来执行
先看看 TimeInterpolator 和它的直接子类
![](https://img.haomeiwen.com/i1586207/8ef48d8d32d725b8.png)
TimeInterpolator 的 UML
![](https://img.haomeiwen.com/i1586207/de3b22de3cbe035a.jpg)
从 UML 图就可以看出 TimeInterpolator 是一个典型的策略模式,你想使用那种插件器,是客户端的事情,并且结合工厂模式创建各自的插件器
2、ListAdapter
乍一看好像没有见过这个东东呀,但是我说一个你肯定知道 ListView 知道吧,BaseAdapter「实现了 ListAdapter」 知道吧 ,大家以前肯定都使用过 ListView 「虽然现在推荐使用 RecyclerView ,但是它依然被很多人使用」,它就是一个策略,我们来分析一下
ListAdaper 和它的直接子类
![](https://img.haomeiwen.com/i1586207/fda783cbb7892cde.png)
ListAdapter 的简单的 UML
![](https://img.haomeiwen.com/i1586207/ccba5310c6104488.jpg)
以上只是 ListAdapter 简单的一个 UML 图「问题说明即可,真正的 ListAdapter 比这复杂多」,从上面可以看到 ListAdapter 典型的一个策略模式,有兴趣的朋友可以自行跟一下源码
3、RecyclerView.LayoutManager
RecyclerView.LayoutManager 和它的子类
![](https://img.haomeiwen.com/i1586207/0d76c9a2a1d79e1a.png)
RecyclerView.LayoutManager 简单的 UML
![](https://img.haomeiwen.com/i1586207/1c9e11079791cc47.jpg)
可以看到 RecyclerView.LayoutManager 也是一个策略模式
其实不知不觉中我们使用了好多策略模式,只是没有注意罢了,细细想想,是不是那么回事,再多例子不再举了。有兴趣的朋友可以自已去扒扒 Android 源码看看那部分使用的是策略模式
四、策略模式和简单工厂模式
策略模式和简单工厂非常相似,结构基本上一样,但是它们侧重点不一样
- 策略模式:是一个行为模式,解决策略的切换和扩展,让策略独立于客户端
- 简单工厂模式:是一种创建模式「创建对象」,接收指令创建出具体的对象,让对象的创建和具体的使用客户无关
但是我们在策略模式中可以使用简单工厂模式「把生成策略这一过程使用工厂去实现,这样好不好呢?适合就是最好的」
五、策略模式的优缺点
既然策略模式使用这么广泛,那么策略模式是不是就是无敌了呢,没有一点点缺点?肯定不是的。
优点:
- 1、结构清晰,把策略分离成一个个单独的类「替换了传统的 if else」
- 2、代码耦合度降低,安全性提高「各个策略的细节被屏蔽」
缺点:
- 1、客户端必须要知道所有的策略类,否则你不知道该使用那个策略,所以策略模式适用于提前知道所有策略的情况下
- 2、增加了类的编写,本来只需要 if else 即可「但是这是所有模式和架构的通病呀」
到此为止我们简单明了的介绍完了策略模式,最后说一下:点赞是一种美德