我的设计模式之路:策略模式

设计模式是老生常谈了,在网络上千篇一律,看的越多越容易混淆,所以最好的办法是自己实现一下,用自己的话总结,这样子我们对它的理解才会深刻,才会将其灵活的运用到项目中。我们以一个场景来展开对策略模式的概念理解。

场景
公司有个老项目,里面涉及到一块关于直播推拉流线路切换的功能。以前的同事呢,估计是赶进度,把所有的cdn厂商的地址都写在一个类里面管理。
这里写图片描述
功能是完整实现了,但是对于其他没有接触该模块的同事初次接触来说,实在是有点乱,代码的可读性不好,如果再要加几个厂商的话,只能够在这个类里面去修改,也很繁琐。所以,我就想,能不能重构下,让代码读起来很清晰,后期去扩展的话也容易。
我们来分析下这个场景:
(1)有很多厂商可以让我们选择,那么最好是一个厂商就是一个类,它自己干自己的事,不跟其他的厂商搅和在一起,这样子看起来一目了然,代码读起来也清晰;
(2)既然是推拉流切换的问题,不管有多少个厂商,它们要实现的功能都是相同的。换句话说,就是都需要提供拉流,推流地址等功能;
(3)程序调用的话,总不能每次自己去判断厂商,然后调用对应厂商提供的功能,这样子的话,只要我们增加一个厂商,代码调用处就需要再次进行修改,违反了开闭原则,最好是提供一个管理给外部调用,外部不用管到底要使用什么策略,只要提供功能给它就好了。

我们来总结下:一个厂商就是一个策略,要实现的功能就是这个策略的抽象,提供一个管理给外部调用就是给外部一个引用,可以去执行策略。所以策略模式就是定义了一系列相同功能的策略集合。它有3个要素策略要实现的功能(定义成接口)、具体的策略(实现接口)、管理策略的(给外部调用的引用类)。当然,这是我自己的理解,仅供大家参考。

运用
分析完了,我们动手实践下吧。首先,我们来看下结构:
这里写图片描述
1-策略模式接口:定义一系列方法;
2、3-具体的策略
4、给外部调用的策略管理类

1、策略接口

/**
 * cdn策略接口
 * Created by cjy on 2018/7/25.
 */

public interface ICDNStrategry {
    /**
     * 加载数据
     */
    void load();

    /**
     * 获取拉流host地址
     * @return
     */
    String getPullHost();

    /**
     * 获取推流host地址
     * @return
     */
    String getPushHost();

    /**
     * 获取拉流url地址
     * @return
     */
    String getPullUrl(CDNPushInfo info);

    /**
     * 获取推流url地址
     * @return
     */
    String getPushUrl(CDNPushInfo info);

    /**
     * ping推流地址
     * @param info
     */
    void pushPing(CDNPushInfo info);

    /**
     * 获取策略的类型
     * @return
     */
    int getType();

2、具体策略的实现

package com.kaopu.xylive.tools.cdn;

import com.cyjh.util.StringUtil;
import com.kaopu.xylive.bean.cdn.CDNNewAddressInfo;
import com.kaopu.xylive.bean.cdn.CDNNewInfo;
import com.kaopu.xylive.bean.cdn.CDNPushInfo;
import com.kaopu.xylive.menum.ECDNType;
import com.kaopu.xylive.tools.http.HttpUtil;
import com.kaopu.xylive.tools.preset.PresetManager;
import com.kaopu.xylive.tools.utils.CLog;
import com.kaopu.xylive.tools.utils.Util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import rx.Subscriber;

/**
 * 网宿CDN策略
 * Created by cjy on 2018/7/25.
 */

public class WSStrategry implements ICDNStrategry{
    private final String TAG = WSStrategry.class.getSimpleName();

    /**
     * 网宿拉流ip地址集合
     */
    private List<String> mWSPullIpList;
    /**
     * 当前网宿拉流ip下标
     */
    private int mCurrWSPullIpIndex;
    /**
     * 网宿推流ip地址集合
     */
    private List<String> mWSPushIpList;
    /**
     * 当前网宿推流ip下标
     */
    private int mCurrWSPushIpIndex;

    public static WSStrategry create(){
        return new WSStrategry();
    }

    @Override
    public void load() {
        List<CDNNewInfo> infos = PresetManager.getInstance().getNewCDNAddressInfo();
        CDNNewInfo info = CDNUtil.getMainNewCDNAddressInfo(infos);
        if (info == null) {
            return;
        }
        loadPull(info.RtmpPullFlowAddress);
        loadPush(info.PushFlowAddress);
    }

    /**
     * 请求拉流ip
     * @param infos
     */
    public void loadPull(List<CDNNewAddressInfo> infos ){
        if (!Util.isCollectionEmpty(infos)) {
            for (CDNNewAddressInfo info :
                    infos) {
                HttpUtil.loadWSPull(new WSPullSubscriber(), "rtmp://" + info.Address.toString().trim() + "/", 1, 1, PresetManager.getInstance().getIP());
            }
        }
    }

    /**
     * 请求推流ip
     * @param info
     */
    public void loadPush(CDNNewAddressInfo info ){
        try {
            if (info == null || StringUtil.isEmpty(info.Address)) {
                return;
            }
            HttpUtil.loadWSPull(new WSPushSubscriber(), "rtmp://" + info.Address.toString().trim() + "/", 5, 1, PresetManager.getInstance().getIP());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取拉流的host地址
     * @return
     */
    @Override
    public String getPullHost() {
        if (Util.isCollectionEmpty(mWSPullIpList)) {
            return "";
        }
        mCurrWSPullIpIndex = mCurrWSPullIpIndex % mWSPullIpList.size();
        return mWSPullIpList.get(mCurrWSPullIpIndex);
    }

    /**
     * 获取推流的host地址
     * @return
     */
    @Override
    public String getPushHost() {
        if (Util.isCollectionEmpty(mWSPushIpList)) {
            return "";
        }
        mCurrWSPushIpIndex = mCurrWSPushIpIndex % mWSPushIpList.size();
        return mWSPushIpList.get(mCurrWSPushIpIndex);
    }

    /**
     * 获取拉流地址
     * @param info
     * @return
     */
    @Override
    public String getPullUrl(CDNPushInfo info) {
        String url = "";
        String newHost = getPullHost();
        url = getWSNewUrl(info.Address, newHost);
        return url;
    }

    /**
     * 获取推流地址
     * @param info
     * @return
     */
    @Override
    public String getPushUrl(CDNPushInfo info) {
        String url = info.Address;
        String newHost = getPushHost();
        url = getWSNewUrl(info.Address, newHost);
        return url;
    }

    /**
     * ping推流地址
     * @param info
     */
    @Override
    public void pushPing(CDNPushInfo info) {
        if (!Util.isCollectionEmpty(mWSPullIpList)) {
            CDNUtil.minConnHostTask(mWSPushIpList).subscribe(new Subscriber<Integer>() {
                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }

                @Override
                public void onNext(Integer o) {
                    mCurrWSPushIpIndex = o;
                }
            });
        }
    }

    @Override
    public int getType() {
        return ECDNType.E_WS.getIntValue();
    }

    /**
     * 获取拉流ip的回调
     */
    private class WSPullSubscriber extends Subscriber {

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onNext(Object o) {
            try {
                mWSPullIpList = getNodeWSList((String) o);
                CLog.e(TAG, "网宿ip=" + o.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取推流ip的回调
     */
    private class WSPushSubscriber extends Subscriber {

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onNext(Object o) {
            try {
                mWSPushIpList = getNodeWSList((String) o);
                CLog.e(TAG, "网宿推流ip=" + o.toString());
                CDNUtil.minConnHostTask(mWSPushIpList).subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(Integer o) {
                        mCurrWSPushIpIndex = o;
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获得网宿组装节点列表
     */
    private List<String> getNodeWSList(String str) {
        List<String> pullList = new ArrayList<>();
        String hosts = str;
        String[] hsotArr = hosts.split("\n");
        pullList = Arrays.asList(hsotArr);
        return pullList;
    }

    /**
     * 网宿地址加上ip
     */

    public String getWSNewUrl(String oldurl, String newHost) {
        if (!StringUtil.isEmpty(newHost)) {
            try {
                String ourl1 = oldurl.substring("rtmp://".length(), oldurl.length());
                String[] ourl2 = ourl1.split("/");
                String[] url3 =  ourl2[2].split("\\?");
                StringBuilder sb = new StringBuilder();
                sb.append("rtmp://");
                sb.append(newHost);
                sb.append("/");
                sb.append(ourl2[1]);
                sb.append("/");
                sb.append(url3[0]);
                sb.append("?wsHost=");
                sb.append(ourl2[0]);
                if(url3.length>=2){
                    sb.append("&");
                    sb.append(url3[1]);
                }
//                sb.append("&wsiphost=");
//                sb.append(PresetManager.getInstance().getIP());
                String newUrl = sb.toString();
                CLog.e(TAG, "newUrl=" + newUrl);
                return newUrl;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return oldurl;
    }

}

3、策略管理(给外部的引用类)

package com.kaopu.xylive.tools.cdn;

import com.kaopu.xylive.bean.cdn.CDNPushInfo;

import java.util.ArrayList;
import java.util.List;

/**
 * CDN策略管理
 * Created by cjy on 2018/7/25.
 */

public class CDNStrategryManager implements ICDNStrategry {
    /**
     * 单例对象
     */
    private static volatile CDNStrategryManager mInstance = null;

    /**
     * 策略列表,主要是为了支持多策略
     */
    private List<ICDNStrategry> mCdnStrategryList = new ArrayList<>();
    /**
     * 当前地址
     */
    private String mCurrPath;

    private CDNStrategryManager() {
    }

    public static CDNStrategryManager getInstance(){
        if (mInstance == null){
            synchronized (CDNStrategryManager.class){
                if (mInstance == null)
                    mInstance = new CDNStrategryManager();
            }
        }
        return mInstance;
    }

    /**
     * 添加策略
     * @param strategry
     */
    public void addStrategry(ICDNStrategry strategry){
        boolean isAdded = false;
        //遍历策略,如果已经添加过了,就不再添加了
        for (ICDNStrategry temp : mCdnStrategryList){
            if (temp.getClass().getSimpleName().equals(strategry.getClass().getSimpleName())){
                isAdded = true;
                break;
            }
        }
        if (!isAdded) {
            mCdnStrategryList.add(strategry);
        }
    }

    /**
     * 移除所有策略
     */
    public void removeAll(){
        mCdnStrategryList.clear();
    }

    /**
     * 获取拉流地址
     * @param info
     * @return
     */
    @Override
    public String getPullUrl(CDNPushInfo info) {
        int index = findStrategry(info);
        String url = info.Address;
        String newHost = mCdnStrategryList.get(index).getPullHost();
        if (index != -1) {
            url = mCdnStrategryList.get(index).getPullUrl(info);
        } else {
            url = CDNUtil.getNewUrl(info.Address, newHost);
        }
        mCurrPath = url;
        return url;
    }

    /**
     * 获取推流地址
     * @param info
     * @return
     */
    @Override
    public String getPushUrl(CDNPushInfo info) {
        int index = findStrategry(info);
        String url = info.Address;
        String newHost = mCdnStrategryList.get(index).getPushHost();
        if (index != -1) {
            url = mCdnStrategryList.get(index).getPushUrl(info);
        } else {
            url = CDNUtil.getNewUrl(info.Address, newHost);
        }
        mCurrPath = url;
        return url;
    }

    /**
     * ping 推流地址
     * @param info
     */
    @Override
    public void pushPing(CDNPushInfo info) {
        int index = findStrategry(info);
        if (index != -1) {
            mCdnStrategryList.get(index).pushPing(info);
        }
    }

    /**
     * 查找对应的策略
     * @param info
     * @return
     */
    private int findStrategry(CDNPushInfo info){
        int index = -1;
        int size = mCdnStrategryList.size();
        //遍历策略,查找对应的策略
        for (int i = 0 ; i < size && size > 0; i++){
            ICDNStrategry strategry = mCdnStrategryList.get(i);
            if (strategry.getType() == info.ManufacturerType){
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * 获取当前地址
     * @return
     */
    public String getCurrPath(){
        return mCurrPath;
    }

    /**
     * 加载数据,初始化推流、拉流地址
     */
    @Override
    public void load() {
        int size = mCdnStrategryList.size();
        for (int i = 0 ; i < size && size > 0; i++){
            ICDNStrategry strategry = mCdnStrategryList.get(i);
            strategry.load();
        }
    }

    @Override
    public String getPullHost() {
        return null;
    }

    @Override
    public String getPushHost() {
        return null;
    }

    @Override
    public int getType() {
        return 0;
    }
}

一般程序使用策略的话,可以在具体使用的地方调用,也可以在应用初始化中就定义策略,统一管理。
我使用的就是第2种方法:在Application的onCreate中初始化下策略:

 CDNStrategryManager.getInstance().addStrategry(WSStrategry.create());
 CDNStrategryManager.getInstance().addStrategry(BYSStrategry.create());

总结
相信大家看完之后,就会明白策略模式一点也不复杂,只要掌握好三要素,就能在项目中灵活使用了。如果还有什么问题的话,欢迎大家在评论下留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值