03、策略模式--Strategy

本节大纲

版权声明:本文为博主原创文章,未经博主允许不得转载

PS:转载请注明出处
作者: TigerChain
地址: http://www.jianshu.com/p/135532803cdb
本文出自 TigerChain 简书 人人都会设计模式

教程简介

  • 1、阅读对象
    本篇教程适合新手阅读,老手直接略过
  • 2、教程难度
    初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
  • 3、Demo 地址
    https://github.com/tigerchain/DesignPattern 请看 Strategy 部分

正文

一、什么是策略模式

1、 生活中的策略

比如说我要出行旅游,那么出行方式有--飞机、自驾游、火车等,这几种方式就是策略。再比如:某大型商场搞活动--满 100 元送杯子,满 300 减 50 ,满 1000 元抽奖「一等将彩色电视机」,这种活动也是策略。在游戏中,我们打一个普通的怪使用普通的招即可,打大 BOSS 就要是用大招,这也是一种策略 ...

2、程序中的策略

就是对各个算法的一个封装「不是实现算法,而是封装算法」,让客户端非常容易的调用,省掉了客户端 if else 恶心的判断,让客户端独立于各个策略

这里举一个简单的例子:比如我们在 Android 中一定会使用到 http 网络请求,请求库太多了,大概有 AsyncHttpclient,OkhttpClient,Volley 等「具体的策略」,那么我们完全可以使用一个策略模式,定义一个抽像策略,然后把各个请求策略封装,客户想使用哪个就使用哪个,非常灵活和方便

策略模式和简单工厂很相似,确有不同,策略是一种行为模式,而简单工厂是创建型模式「创建对象」 后面再说

策略模式的定义

策略是对算法的封装,是一种形为模式,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换

策略的特点

  • 是一种行为模式,对算法封装,使得客户端独立于各个策略
  • 扩展性强,添加策略无非就是添加一个具体的实现类而已,代价非常低

策略模式的结构

角色 类别 说明
Strategy 抽象的策略 是一个接口或抽象类
ConcreteStrategy 具体的策略类 实现了抽象的策略
Context 一个普通的类 上下文环境,持有 Stragegy 的引用

策略模式简单的 UML

策略模式简单的 UML

二、策略模式举例

1、曹操败走华荣道

我们知道三国演义中曹操败走华容道的故事,相传在赤壁之战之后,曹操的船舰被刘备烧了,曹操逃离时面前有两条路:1、平坦的大路。2、泥泞的华容道。面对这两条路,曹操没有选择大路而选择有炊烟的小路「华容道路」,理由---实则虚之,虚则实之,那么曹操在选择道路的时候其实就是选择策略

败走华容道的简单的 UML

败走华荣道

根据 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

出行策略 UML

代码实现

大家根据出行的 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

MVP+策略登录 UML

根据 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 大家自行查看

mvpLoginStrategy.png

怎么样,通过以上几个例子,相信我们对策略模式有了很好的理解了

  • 6、最后运行看一下
login.gif

demo 没有实现完毕,其中 Retorfit 和 Volley 没有完善,有兴趣的可以自行完善

Demo 地址:https://github.com/tigerchain/mvp-rx-loginStrategy

三、Android 源码中的策略模式

1、TimeInterpolator 时间插值器

做过动画的朋友都知道,插值器的概念,一句话就是:设置不同的插值器,动画可以以不同的速度模型来执行

先看看 TimeInterpolator 和它的直接子类

TimeInterpolator 和它的子类

TimeInterpolator 的 UML

TimeInterpolator 简单的 UML

从 UML 图就可以看出 TimeInterpolator 是一个典型的策略模式,你想使用那种插件器,是客户端的事情,并且结合工厂模式创建各自的插件器

2、ListAdapter

乍一看好像没有见过这个东东呀,但是我说一个你肯定知道 ListView 知道吧,BaseAdapter「实现了 ListAdapter」 知道吧 ,大家以前肯定都使用过 ListView 「虽然现在推荐使用 RecyclerView ,但是它依然被很多人使用」,它就是一个策略,我们来分析一下

ListAdaper 和它的直接子类

ListAdapter 和它的子类

ListAdapter 的简单的 UML

ListAdapter 简单的 UML

以上只是 ListAdapter 简单的一个 UML 图「问题说明即可,真正的 ListAdapter 比这复杂多」,从上面可以看到 ListAdapter 典型的一个策略模式,有兴趣的朋友可以自行跟一下源码

3、RecyclerView.LayoutManager

RecyclerView.LayoutManager 和它的子类

RecyclerView.LayoutManager 和它的子类

RecyclerView.LayoutManager 简单的 UML

RecyclerView.LayoutManager 简单的 UML

可以看到 RecyclerView.LayoutManager 也是一个策略模式

其实不知不觉中我们使用了好多策略模式,只是没有注意罢了,细细想想,是不是那么回事,再多例子不再举了。有兴趣的朋友可以自已去扒扒 Android 源码看看那部分使用的是策略模式

四、策略模式和简单工厂模式

策略模式和简单工厂非常相似,结构基本上一样,但是它们侧重点不一样

  • 策略模式:是一个行为模式,解决策略的切换和扩展,让策略独立于客户端
  • 简单工厂模式:是一种创建模式「创建对象」,接收指令创建出具体的对象,让对象的创建和具体的使用客户无关

但是我们在策略模式中可以使用简单工厂模式「把生成策略这一过程使用工厂去实现,这样好不好呢?适合就是最好的」

五、策略模式的优缺点

既然策略模式使用这么广泛,那么策略模式是不是就是无敌了呢,没有一点点缺点?肯定不是的。

优点:

  • 1、结构清晰,把策略分离成一个个单独的类「替换了传统的 if else」
  • 2、代码耦合度降低,安全性提高「各个策略的细节被屏蔽」

缺点:

  • 1、客户端必须要知道所有的策略类,否则你不知道该使用那个策略,所以策略模式适用于提前知道所有策略的情况下
  • 2、增加了类的编写,本来只需要 if else 即可「但是这是所有模式和架构的通病呀」

到此为止我们简单明了的介绍完了策略模式,最后说一下:点赞是一种美德

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,848评论 25 707
  • 写给母亲(一) 能想到的是你转身回望的笑 它消失在了时间里 能想到的是你匆匆忙碌的背影 它消失在了时间里 能想到的...
    屈永宁阅读 197评论 6 4