人人都会设计模式:03-策略模式--Strategy

人人都会设计模式:03-策略模式--Strategy

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

公众号: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

人人都会设计模式:03-策略模式--Strategy

二、策略模式举例

1、曹操败走华荣道

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

败走华容道的简单的 UML

人人都会设计模式:03-策略模式--Strategy

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

人人都会设计模式:03-策略模式--Strategy

代码实现

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

人人都会设计模式:03-策略模式--Strategy

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

人人都会设计模式:03-策略模式--Strategy

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

  • 6、最后运行看一下

人人都会设计模式:03-策略模式--Strategy

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

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

三、Android 源码中的策略模式

1、TimeInterpolator 时间插值器

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

先看看 TimeInterpolator 和它的直接子类

人人都会设计模式:03-策略模式--Strategy

TimeInterpolator 的 UML

人人都会设计模式:03-策略模式--Strategy

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

2、ListAdapter

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

ListAdaper 和它的直接子类

人人都会设计模式:03-策略模式--Strategy

ListAdapter 的简单的 UML

人人都会设计模式:03-策略模式--Strategy

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

3、RecyclerView.LayoutManager

RecyclerView.LayoutManager 和它的子类

人人都会设计模式:03-策略模式--Strategy

RecyclerView.LayoutManager 简单的 UML

人人都会设计模式:03-策略模式--Strategy

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

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

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

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

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

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

五、策略模式的优缺点

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

优点:

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

缺点:

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

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

上一篇:CROND 定时任务 - 学习


下一篇:人人都会设计模式:01-简单工厂模式--SimpleFactory