策略模式之實戰

策略模式屬於對象的行爲模式。其用意是針對一組算法,將每個算法封裝到具備共同接口的獨立的類中,從而使得它們能夠相互替換。策略模式使得算法能夠在不影響到客戶端的狀況下發生變化。——《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

後記

網上關於策略模式的文章很是之多,但大多都是介紹了策略模式是什麼,爲了解決什麼問題的,可是少有結合具體業務來說解的,看完以後感受是明白了原理,但總感受有些困惑,但願看完個人重構經歷,可以進一步加深大家的理解。函數

相關文章
相關標籤/搜索