<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 一個快速切換一個底層實現的思路分享

        現實場景往往是這樣,我們應對一個需求,很快就會有一個處理方法了,然后根據需求做了一個還不錯的實現。因為實現了功能,業務很happy,老板很開心,all the world is beatiful.

        但隨著公司的發展,有人實現了一套底層的標準組件,按要求你必須要接入他那個,他的功能與你類似,但你必須要切換成那個。且不論其實現的質量怎么樣,但他肯定是有一些優勢的,不過他作為標準套件,不可能完全同你的需求一致。因此,這必定涉及到改造的問題。

        一般這種情況下,我們是不太愿意接的,畢竟代碼跑得好好的,誰愿意動呢?而且別人的實現如何,還沒有經過考驗,冒然接入,可能帶來比較大的鍋呢。(從0到1沒人關注準確性,但從1到到1.1就會有人關注準確性了,換句話說這叫兼容性)

        但是,往往迫于壓力,我們又不得不接。

        這時候我們有兩種做法,一種是硬著頭皮直接改代碼為別人的方式。這種處理簡單粗暴,而且沒有后顧之憂。不過,隨之而來的,就是大面積的回歸測試,以及一些可能測試不到的點,意味著代碼的回滾。對于一些線上運維比較方便的地方,也許我們是可以這樣干。但這并不是本文推薦的做法,也不做更多討論。

        更穩妥的做法,應該是在保有現有實現的情況下,進行新實現的接入,至少你還可以對照嘛。進可攻,退可守。


     

    1. 快速接入新實現1:抽象類

        既然我們不敢直接替換現有的實現,那么就得保留兩種實現,所以可以用抽象類的方式,保持原有實現的同時,切入新的實現。是個比較直觀的想法了,具體實現如下:

    1. 抽象一個公共類出來

     

    public abstract class AbstractRedisOperate {
    
        private AbstractRedisOperate impl;
    
        public AbstractRedisOperate() {
            String strategy = "a";  // from config
            if("a".equals(strategy)) {
                impl = new RedisOperateA1Imp();
            }
            else {
                impl = new RedisOperateB2Imp();
            }
        }
    
        // 示例操作接口
        public void set(String key, String value);
    }

     

    2. 實現兩個具體類

    // 實現1,完全依賴于抽象類實現(舊有功能)
    public class RedisOperateOldImp extends AbstractRedisOperate {
    
    }
    
    // 實現2,新接入的實現
    public class RedisOperateB2Imp extends AbstractRedisOperate {
    
        @Override
        public void set(String key, String value) {
            System.out.println("this is b's implement...");
        }
    }

     

    3. 保持原有的實現類入口,將其實現變成一個外觀類或者叫適配器類

    // 加載入口
    @Service
    public class RedisOperateFacade extends AbstractRedisOperate {
    
        public RedisOperateFacade() {
            // case1. 直接交由父類處理
            super();
        }
    
        @Override
        public void set(String key, String value) {
            // fake impl
        }
    }

     

        以上實現有什么好處呢?首先,現有的實現被抽離,且不用做改動被保留了下來。新的實現類自行實現一個新的。通過一個公共的切換開關,進行切換處理。這樣一來,既可以保證接入了新實現,而且也保留了舊實現,在出未知故障時,可以回切實現。

        以上實現有什么問題?

        當我們運行上面的代碼時,發現報錯了,為什么?因為出現了死循環。雖然我們只加載了一個 Facade 的實現,但是在調用super時,super會反過來加載具體的實現,具體的實現又會去加載抽象類super,如此循環往復,直到棧溢出。也叫出現了死循環。


     

    2. 解決簡單抽象帶來的問題

        上一節我們已經知道為什么出現加載失敗的問題,其實就是一個循環依賴問題。如何解決呢?

        其實就是簡單地移動下代碼,不要將判斷放在默認構造器中,由具體的外觀類進行處理,加載策略由外觀類決定,而非具體的實現類或抽象類。

        具體操作如下:

    // 1. 外觀類控制加載
    @Service
    public class RedisOperateFacade extends AbstractRedisOperate {
    
        public RedisOperateFacade() {
            // case1. 直接交由父類處理
            // super();
            // case2. 決定加載哪個實現
            String strategy = "a";  // from config center
            if("a".equals(strategy)) {
                setImpl(new RedisOperateOldImp());
            }
            else {
                setImpl(new RedisOperateB2Imp());
            }
        }
    
    }
    // 2. 各實現保持自身不動
    public class RedisOperateOldImp extends AbstractRedisOperate {
        // old impl...
    }
    
    public class RedisOperateB2Imp extends AbstractRedisOperate {
    
        // new impl...
        @Override
        public void set(String key, String value) {
            System.out.println("this is b's implement...");
        }
    }
    
    // 3. 抽象類不再進行加載策略處理
    public abstract class AbstractRedisOperate {
        // 持有具體實現
        private AbstractRedisOperate impl;
    
        public AbstractRedisOperate() {
        }
    
        protected void setImpl(AbstractRedisOperate impl) {
            this.impl = impl;
        }
    
        // 示例操作接口, old impl...
        public abstract void set(String key, String value);
    }

     

        做了微小的改動,將加載策略從抽象類中轉移到外觀類中,就可以達到正確的加載效果了。實際上,為了簡單起見,我們甚至可以將原有的實現全部copy到抽象類中,而新增的一個原有實現類,則什么也不用做,只需新增一個空繼承抽象類即可。而新的實現,則完全覆蓋現有的具體實現就可以了。從而達到一個最小的改動,而且順利接入一個新實現的效果。

        但是如果依賴于抽象類的具體實現的話,會帶來一個問題,那就是如果我們的子類實現得不完善,比如遺漏了一些實現時,代碼本身并不會報錯提示。這就給我們帶來了潛在的風險,因為那樣就會變成,一部分是舊有實現,另一部分是新的實現。這可能會有兩個問題:一是兩個實現有一個報錯一個正常;二是無法正常切換回滾,兩種實現耦合在了一起。


     

    3. 更完善的方案:基于接口的不同實現

        怎么辦呢?我們可以再抽象一層接口出來,各實現針對接口處理,只有外觀類繼承了抽象類,而且抽象類同時也實現了接口定義。這樣的話,就保證了各實現的完整性,以及外觀類的統一性了。這里,我利用的是語法的強制特性,即接口必須得到實現的語義,進行代碼準確性的保證。(當然了,所有的現實場景,接口都必須有相應的實現,因為外部可見只有接口,如果不實現則必定不合法)

    具體實現如下:

    //1. 統一接口定義
    public interface UnifiedRedisOperate {
    
        void set(String key, String value, int ttl);
    
        // more interface definitions...
    }
    // 2. 各子實現類
    public class RedisOperateOldImp implements UnifiedRedisOperate {
    
        @Override
        public void set(String key, String value) {
            System.out.println("this is a's implement...");
        }
    }
    public class RedisOperateB2Imp implements UnifiedRedisOperate {
    
        @Override
        public void set(String key, String value) {
            System.out.println("this is b's implement...");
        }
    }
    // 3. 外觀類的實現
    @Service
    public class RedisOperateFacade extends AbstractRedisOperate {
    
        public RedisOperateFacade() {
            // case1. 直接交由父類處理
            // super();
            // case2. 外觀類控制加載
            String strategy = "a";  // from config center
            if("a".equals(strategy)) {
                setImpl(new RedisOperateOldImp());
            }
            else {
                setImpl(new RedisOperateB2Imp());
            }
        }
    
    }
    public abstract class AbstractRedisOperate implements UnifiedRedisOperate {
    
        private UnifiedRedisOperate impl;
    
        protected void setImpl(UnifiedRedisOperate impl) {
            this.impl = impl;
        }
    
        // 接口委托
        public void set(String key, String value) {
            impl.set(key, value);
        }
    
        // more delegates...
    }

     

        看起來是多增加了一個接口類,但是實際上整個代碼更加清晰易讀了。實際上,一個好的設計,最初應該也是基于接口的(即面向接口編程),而我們在這里重新抽象出一個接口類來,實際上就是彌補之前設計的不足,也算是一種重構了。所有的實現都基于接口,一個實現都不能少,從而減少了出錯的概率。

        如此,我們就可以放心的進行生產切換了。

     文章原創發布微信公眾號地址: 一個快速切換一個底層實現的思路分享

    posted @ 2022-06-26 11:32  等你歸去來  閱讀(200)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看