策略模式之实战

策略模式属于对象的行为模式。其用意是针对一组算法,将每个算法封装到具备共同接口的独立的类中,从而使得它们能够相互替换。策略模式使得算法能够在不影响到客户端的状况下发生变化。——《JAVA与设计模式》java

下面是策略模式的类图: 算法

Strategy
策略模式涉及到三个对象:

  • 环境上下文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来肯定一个具体的跳转,而后依赖咱们的抽象接口实现各页面的具体跳转须要。听个人!用策略模式,敲定方案后咱们说干就干。首先上个类图看下: bash

DeepLink

  • 首先咱们定义一个接口,这个接口抽象了跳转的全部操做。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);
        }
    }
复制代码

是否是清爽了许多,并且就算增长再多的跳转界面,这里的代码也不用变化,之后可达鸭不再用担忧新增跳转界面了。ide

后记

网上关于策略模式的文章很是之多,但大多都是介绍了策略模式是什么,为了解决什么问题的,可是少有结合具体业务来说解的,看完以后感受是明白了原理,但总感受有些困惑,但愿看完个人重构经历,可以进一步加深大家的理解。函数

相关文章
相关标签/搜索