设计模式是老生常谈了,在网络上千篇一律,看的越多越容易混淆,所以最好的办法是自己实现一下,用自己的话总结,这样子我们对它的理解才会深刻,才会将其灵活的运用到项目中。我们以一个场景来展开对策略模式的概念理解。
场景
公司有个老项目,里面涉及到一块关于直播推拉流线路切换的功能。以前的同事呢,估计是赶进度,把所有的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());
总结
相信大家看完之后,就会明白策略模式一点也不复杂,只要掌握好三要素,就能在项目中灵活使用了。如果还有什么问题的话,欢迎大家在评论下留言。