策略模式之实战

2,266 阅读6分钟

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。——《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来确定一个具体的跳转,然后依赖我们的抽象接口实现各页面的具体跳转需要。听我的!用策略模式,敲定方案后我们说干就干。首先上个类图看下:

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);
        }
    }

是不是清爽了许多,而且就算增加再多的跳转界面,这里的代码也不用变化,以后可达鸭再也不用担心新增跳转界面了。

后记

网上关于策略模式的文章非常之多,但大多都是介绍了策略模式是什么,为了解决什么问题的,但是少有结合具体业务来讲解的,看完之后感觉是明白了原理,但总感觉有些困惑,希望看完我的重构经历,能够进一步加深你们的理解。