策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。——《JAVA与设计模式》
下面是策略模式的类图:
策略模式涉及到三个对象:- 环境上下文Context,他聚合了策略模式的抽象接口,通过接口来调用具体的实现类,并对业务层提供方法来调用具体算法函数。
- 算法抽象接口IStrategy,该接口是一组算法的抽象,向上提供了访问入口,向下起到了归类的作用。
- 接口IStrategy的实现类,这里对具体的算法进行封装。
工作中遇到的问题
在我的工程中,有这样一处场景,在App文章详情页面的正文中有时候会有一些超链接,点击这个链接要求能够跳转到另外约定好的界面,那这个约定是怎样的呢?在此之前我们先看一下Uri的结构。
[scheme:][//host:port][path][?query][#fragment]
- 我们通过约定scheme来确定这是个内链还是外链,外链就跳转浏览器,内链就要跳转到我们应用的内部界面;
- 通过约定host来确定这个链接应该跳转到的界面属于哪一个模块;
- 通过约定path来确定这个界面最终应该是哪一个;
- 而query就是我们跳转时需要携带的参数。 有了这些约定就好办了,然后我就开始欢快的撸起代码来了,很快我就写好了一个工具类,大概就是这样子的。
public class DeepLinkUtils {
public static void jumpToTarget(Context context, Uri uri) {
if (uri == null || uri.getHost() == null) {return;}
switch (uri.getHost()) {
case "news":
String newsPath = uri.getPath();
if ("/detail".equals(newsPath)) {
..........
} else if ("/topic".equals(newsPath)) {
.........
} else {
.........
}
break;
case "flash":
.........
break;
default:
.........
break;
}
}
}
然而可达鸭觉得事情没有这么简单,很快头疼的事就来了,业务总是会膨胀的,没多久就收到了通知,这个界面也要支持下链接跳转……省略N多次这种支持,然后这个方法就变成将近二十个case,某些case中还有数量不等的if…else…,此处就不贴那恐怖的代码了,发挥你们的聪明才智想象一下吧。长长的条件分支结构不仅视觉上震撼,修改和新增代码都让人如履薄冰,生怕一个眼花写到了错误的分支中,而且找到对应的分支就足够让人头晕目眩了,此刻我只觉得有一只苍蝇一直在我的代码里嗡嗡乱飞。
这样下去和咸鱼有什么分别(主要是这咸鱼当的恶心啊),我决定重构The Fucking Codes。
解决方案
可达鸭眉头一皱,计上心头,鸭有一计,可平代码之乱,不好意思串戏了。首先我们分析下我们这个需求,虽然有很多的页面需要跳转,但是他们的跳转我们可以抽象出相同点,然后根据上面的URI规则可以通过host+path来确定一个具体的跳转,然后依赖我们的抽象接口实现各页面的具体跳转需要。听我的!用策略模式,敲定方案后我们说干就干。首先上个类图看下:
- 首先我们定义一个接口,这个接口抽象了跳转的所有操作。uriTransformer方法将业务上约定好的Uri转换成我们界面对应的路由表里需要的Uri,然后通过路由框架跳转;navigationParams方法将业务上约定的Uri里的query部分的键值存到Map中,提供跳转时需要的参数;needSendEvents方法用来决定是否发出一个EventBus消息,正常的跳转是不需要理会此方法的,所以才会有SimpleDeepLinkStrategy这个抽象类。
public interface IDeepLinkStrategy {
Uri uriTransformer(Uri uri);
Map<String,String> navigationParams(Uri uri);
boolean needSendEvents(Uri uri);
}
- SimpleDeepLinkStrategy这是一个抽象类,因为接口中有特殊方法,除了个别的跳转需要实现此方法外,其它的并不需要实现这个方法,所以在这个抽象类中做个默认实现,需要的类可以重写这个方法,不需要的也不用关心这个方法。
public abstract class SimpleDeepLinkStrategy implements IDeepLinkStrategy {
@Override
public boolean needSendEvents(Uri uri) {
return false;
}
}
- A 和 B两个实现类就是跳转逻辑的具体实现。
- StrategyFactory类是一个简单工厂类。前面我们说过,根据约定,可以通过host+path来确定一个具体的跳转,也就是可以确定是用A还是用B,那么我们把host+path作为键,具体的实现类作为值存储到Map中。通过这个工厂类暴露的creator方法来返回我们需要的类。
public class StrategyFactory {
private StrategyFactory(){}
/** 缓存策略类实例,由于一个策略可能要多次使用,如果不做缓存,每次都要通过反射实例化一个,内存中的无用对象也会
* 越来越多
*/
private static HashMap<String,IDeepLinkStrategy> strategyCaches = new HashMap<>();
private static class StrategyFactoryHolder{
public static final StrategyFactory INSTANCE = new StrategyFactory();
}
public static StrategyFactory getInstance(){
return StrategyFactoryHolder.INSTANCE;
}
private static HashMap<String,String> classMap = new HashMap<>();
static {
classMap.put("news", ADeepLinkStrategy.class.getName());
classMap.put("news/detail", BDeepLinkStrategy.class.getName());
.......
}
public IDeepLinkStrategy creator(String key){
//如果实例已经存在则直接取出
if (strategyCaches.get(key) != null){
return strategyCaches.get(key);
}
//实例不存在通过策略类名反射得到实例并缓存到strategyCaches里面
String className = classMap.get(key);
if (TextUtils.isEmpty(className)){
className = WebStrategy.class.getName();
}
try {
IDeepLinkStrategy iDeepLinkStrategy = (IDeepLinkStrategy) Class.forName(className).newInstance();
strategyCaches.put(key,iDeepLinkStrategy);
return iDeepLinkStrategy;
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
return null;
}
}
}
- 然后业务层就可以通过DeepLinkStrategyContext来愉快的跳来跳去了。
public class DeepLinkContext {
private static IDeepLinkStrategy mIDeepLinkStrategy;
public static Uri getUri(Uri uri){
return getStrategyInstance(uri).uriTransformer(uri);
}
public static Map<String,String> getParams(Uri uri){
return getStrategyInstance(uri).navigationParams(uri);
}
public static boolean needPostEvents(Uri uri){
return getStrategyInstance(uri).needSendEvents(uri);
}
private static IDeepLinkStrategy getStrategyInstance(Uri uri){
String key = uri.getHost()+uri.getPath();
mIDeepLinkStrategy = StrategyFactory.getInstance().creator(key);
return mIDeepLinkStrategy;
}
}
然后我们来看看上面的工具类变成啥样了:
public class DeepLinkUtils {
public static void jumpToTarget(Context context, Uri uri) {
if (uri == null || uri.getHost() == null) {return;}
Uri routUri =DeepLinkContext.getUri(uri);
if (routUri != null){
Postcard postcard = ARouter.getInstance().build(routUri);
Map<String,String> params = DeepLinkContext.getParams(uri);
if (params!=null){
for (Map.Entry<String, String> entry : params.entrySet()) {
postcard.withString(entry.getKey(),entry.getValue());
}
postcard.navigation(context);
}else {
postcard.navigation(context);
}
}
DeepLinkContext.needPostEvents(uri);
}
}
是不是清爽了许多,而且就算增加再多的跳转界面,这里的代码也不用变化,以后可达鸭再也不用担心新增跳转界面了。
后记
网上关于策略模式的文章非常之多,但大多都是介绍了策略模式是什么,为了解决什么问题的,但是少有结合具体业务来讲解的,看完之后感觉是明白了原理,但总感觉有些困惑,希望看完我的重构经历,能够进一步加深你们的理解。