1. 多圖詳解:七種具體方法增強代碼可擴展性

        共 9637字,需瀏覽 20分鐘

         ·

        2022-05-13 01:37


        JAVA前線?


        歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實際應用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習



        1 六大原則

        在設(shè)計模式中有六大設(shè)計原則:

        單一職責原則:一次做一件事

        里式替換原則:子類擴展父類

        依賴倒置原則:面向接口編程

        接口隔離原則:高內(nèi)聚低耦合

        迪米特法則:最少知道原則

        開閉原則:關(guān)閉修改,開放新增

        我認為在這六個原則中開閉原則最為重要,開閉原則是可擴展性的重要基石。

        第一個原因是需求變化時應該通過新增而不是修改已有代碼實現(xiàn),這樣保證了代碼穩(wěn)定性,避免牽一發(fā)而動全身。

        第二個原因是可以事先定義代碼框架,擴展也是根據(jù)框架擴展,體現(xiàn)了用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié),保證了穩(wěn)定性也保證了靈活性。

        第三個原因是其它五個原則雖然側(cè)重點各有不同,但是都可以包含于開閉原則。

        第四個原因是標準二十三種設(shè)計模式最終都是在遵循開閉原則。

        既然開閉原則如此重要,我們應該怎么在系統(tǒng)設(shè)計時遵循開閉原則呢?


        2 數(shù)據(jù)庫維度

        2.1 設(shè)計類型字段

        我們可以在數(shù)據(jù)表設(shè)計時新增兩列:biz_type、biz_sub_type,這兩個列即使目前用不上,可以先設(shè)置一個默認值。

        biz_type可以指業(yè)務類型,業(yè)務渠道、租戶等等,歸根結(jié)底是用來隔離大業(yè)務類型,假設(shè)后續(xù)業(yè)務新增了一個大業(yè)務類型,那么可以通過這個字段隔離。

        biz_sub_type可以在大業(yè)務類型中細分小業(yè)務,使得業(yè)務更加細化,靈活性更好。


        2.2 設(shè)計擴展字段

        我們可以在數(shù)據(jù)表設(shè)計時新增三列:extend1、extend2、extend3,可以先設(shè)置為空。擴展字段存儲JSON類型信息,存放一些擴展信息、附加信息、松散信息或者是之前未預估到的信息。

        之所以設(shè)置三個擴展字段是為了增加隔離性,例如extend1存放訂單擴展信息,extend2存放商品擴展信息,extend3存放營銷擴展信息。


        2.3 設(shè)計業(yè)務二進制字段

        2.3.1 需求背景

        我們可以在數(shù)據(jù)表設(shè)計時新增業(yè)務二進制字段,這個字段可以很大程度上擴展業(yè)務表意能力。假設(shè)在系統(tǒng)中用戶一共有三種角色:普通用戶、管理員、超級管理員,現(xiàn)在需要設(shè)計一張用戶角色表記錄這類信息。不難設(shè)計出如下方案:

        idnamesuperadminnormal
        101用戶一100
        102用戶二010
        103用戶三001
        104用戶四111

        觀察上表不難得出,用戶一具有超級管理員角色,用戶二具有管理員角色,用戶三具有普通用戶角色,用戶四同時具有三種角色。如果此時新增加一種角色呢?那么新增一個字段即可。


        2.3.2 發(fā)現(xiàn)問題

        按照上述一個字段表示一種角色進行表設(shè)計功能上是沒有問題的,優(yōu)點是容易理解結(jié)構(gòu)清晰,但是我們想一想有沒有什么問題?筆者遇到過如下問題:在復雜業(yè)務環(huán)境一份數(shù)據(jù)可能會使用在不同的場景,例如上述數(shù)據(jù)存儲在MySQL數(shù)據(jù)庫,這一份數(shù)據(jù)還會被用在如下場景:

        檢索數(shù)據(jù)需要同步一份到ES

        業(yè)務方使用此表通過Flink計算業(yè)務指標

        業(yè)務方訂閱此表Binlog消息進行業(yè)務處理

        如果表結(jié)構(gòu)發(fā)生變化,數(shù)據(jù)源之間就要重新進行對接,業(yè)務方也要進行代碼修改,這樣開發(fā)成本比較非常高。有沒有辦法避免此類問題?


        2.3.3 解決方案

        我們可以使用位圖法,這樣同一個字段可以表示多個業(yè)務含義。首先設(shè)計如下數(shù)據(jù)表,userFlag字段暫時不填。

        idnameuser_flag
        101用戶一暫時不填
        102用戶二暫時不填
        103用戶三暫時不填
        104用戶四暫時不填

        設(shè)計位圖每一個bit表示一種角色:



        使用位圖法表示如下數(shù)據(jù)表:

        idnamesuperadminnormal
        101用戶一100
        102用戶二010
        103用戶三001
        104用戶四111

        用戶一位圖如下其十進制數(shù)值等于4:


        用戶二位圖如下其十進制數(shù)值等于2:


        用戶三位圖如下其十進制數(shù)值等于1:


        用戶四位圖如下其十進制數(shù)值等于7:


        根據(jù)上述分析可以填寫數(shù)據(jù)表第三列:

        idnameuser_flag
        101用戶一4
        102用戶二2
        103用戶三1
        104用戶四7

        2.3.4 代碼實例

        定義枚舉時不要直接定義為1、2、4這類數(shù)字,而應該采用位移方式進行定義,這樣使用者可以明白設(shè)計者的意圖。

        /**
        ?*?用戶角色枚舉
        ?*
        ?*?@author?微信公眾號「JAVA前線」
        ?*
        ?*/

        public?enum?UserRoleEnum?{

        ????//?1?->?00000001
        ????NORMAL(1,?"普通用戶"),

        ????//?2?->?00000010
        ????MANAGER(1?<1,?"管理員"),

        ????//?4?->?00000100
        ????SUPER(1?<2,?"超級管理員")

        ????;

        ????private?int?code;
        ????private?String?description;

        ????private?UserRoleEnum(Integer?code,?String?description)?{
        ????????this.code?=?code;
        ????????this.description?=?description;
        ????}

        ????public?String?getDescription()?{
        ????????return?description;
        ????}

        ????public?int?getCode()?{
        ????????return?this.code;
        ????}
        }

        假設(shè)用戶已經(jīng)具有普通用戶角色,我們需要為其增加管理員角色,這就是新增角色,與之對應還有刪除角色和查詢角色,這些操作需要用到為位運算,詳見代碼注釋。

        /**
        ?*?用戶角色枚舉
        ?*
        ?*?@author?微信公眾號「JAVA前線」
        ?*
        ?*/

        public?enum?UserRoleEnum?{

        ????//?1?->?00000001
        ????NORMAL(1,?"普通用戶"),

        ????//?2?->?00000010
        ????MANAGER(1?<1,?"管理員"),

        ????//?4?->?00000100
        ????SUPER(1?<2,?"超級管理員")

        ????;

        ????//?新增角色?->?位或操作
        ????//?oldRole?->?00000001?->?普通用戶
        ????//?addRole?->?00000010?->?新增管理員
        ????//?newRole?->?00000011?->?普通用戶和管理員
        ????public?static?Integer?addRole(Integer?oldRole,?Integer?addRole)?{
        ????????return?oldRole?|?addRole;
        ????}

        ????//?刪除角色?->?位異或操作
        ????//?oldRole?->?00000011?->?普通用戶和管理員
        ????//?delRole?->?00000010?->?刪除管理員
        ????//?newRole?->?00000001?->?普通用戶
        ????public?static?Integer?removeRole(Integer?oldRole,?Integer?delRole)?{
        ????????return?oldRole?^?delRole;
        ????}

        ????//?是否有某種角色?->?位與操作
        ????//?allRole?->?00000011?->?普通用戶和管理員
        ????//?qryRole?->?00000001?->?是否有管理員角色
        ????//?resRole?->?00000001?->?有普通用戶角色
        ????public?static?boolean?hasRole(Integer?allRole,?Integer?qryRole)?{
        ????????return?qryRole?==?(role?&?qryRole);
        ????}

        ????private?int?code;
        ????private?String?description;

        ????private?UserRoleEnum(Integer?code,?String?description)?{
        ????????this.code?=?code;
        ????????this.description?=?description;
        ????}

        ????public?String?getDescription()?{
        ????????return?description;
        ????}

        ????public?int?getCode()?{
        ????????return?this.code;
        ????}

        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(addRole(1,?2));
        ????????System.out.println(removeRole(3,?1));
        ????????System.out.println(hasRole(3,?1));
        ????}
        }

        假設(shè)在運營后臺查詢界面中,需要查詢具有普通用戶角色的用戶數(shù)據(jù),可以使用如下SQL語句:

        select?*?from?user_role?where?(user_flag?&?1)?=?user_flag;
        select?*?from?user_role?where?(user_flag?&?b'0001')?=?user_flag;

        我們也可以使用MyBatis語句:

        <select?id="selectByUserRole"?resultMap="BaseResultMap"?parameterType="java.util.Map">
        ??select?*?from?user_role?
        ??where?user_flag?&?#{userFlag}?=?#{userFlag}
        select>

        <select?id="selectByUserIdAndRole"?resultMap="BaseResultMap"?parameterType="java.util.Map">
        ??select?*?from?user_role?
        ??where?id?=?#{userId}?and?user_flag?&?#{userFlag}?=?#{userFlag}
        select>

        3 接口維度

        3.1 設(shè)計類型入?yún)?/span>

        接口輸入?yún)?shù)一定要設(shè)計類型入?yún)?,可以與數(shù)據(jù)庫biz_type、biz_sub_type相對應,也可以自定義類型最終翻譯與數(shù)據(jù)庫類型相對應。

        如果一開始不用區(qū)分類型,可以先設(shè)置為默認值,但是其核心思想是一定要可以通過類型對輸入?yún)?shù)進行區(qū)分。

        public?class?OrderDTO?{
        ????private?Integer?bizType;
        ????private?Integer?bizSubType;
        ????private?Long?amount;
        ????private?String?goodsId;
        }

        public?Response?createOrder(OrderDTO?order);

        3.2 設(shè)計松散入?yún)?/span>

        接口輸入?yún)?shù)可以設(shè)計Map類型松散參數(shù),松散參數(shù)缺點是表意能力弱,優(yōu)點是靈活性強,可以以較低成本新增參數(shù)。

        public?class?OrderDTO?{
        ????private?Integer?bizType;
        ????private?Integer?bizSubType;
        ????private?Long?amount;
        ????private?String?goodsId;
        ????private?Map?params;
        }

        public?Response?createOrder(OrderDTO?order);

        3.3 設(shè)計接口版本號

        對外接口需要設(shè)計版本號,對于同一個接口允許外部業(yè)務逐漸切到新版本,新老版本接口會共存一段時間,通過版本號區(qū)分。

        /order/1.0/createOrder
        /order/1.1/createOrder

        3.4 縱橫做設(shè)計

        我們分析一個下單場景:當前有ABC三種訂單類型:A訂單價格9折,物流最大重量不能超過9公斤,不支持退款。B訂單價格8折,物流最大重量不能超過8公斤,支持退款。C訂單價格7折,物流最大重量不能超過7公斤,支持退款。按照需求字面含義平鋪直敘地寫代碼也并不難。

        public?class?OrderServiceImpl?implements?OrderService?{

        ????@Resource
        ????private?OrderMapper?orderMapper;

        ????@Override
        ????public?void?createOrder(OrderBO?orderBO)?{
        ????????if?(null?==?orderBO)?{
        ????????????throw?new?RuntimeException("參數(shù)異常");
        ????????}
        ????????if?(OrderTypeEnum.isNotValid(orderBO.getType()))?{
        ????????????throw?new?RuntimeException("參數(shù)異常");
        ????????}
        ????????//?A類型訂單
        ????????if?(OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType()))?{
        ????????????orderBO.setPrice(orderBO.getPrice()?*?0.9);
        ????????????if?(orderBO.getWeight()?>?9)?{
        ????????????????throw?new?RuntimeException("超過物流最大重量");
        ????????????}
        ????????????orderBO.setRefundSupport(Boolean.FALSE);
        ????????}
        ????????//?B類型訂單
        ????????else?if?(OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType()))?{
        ????????????orderBO.setPrice(orderBO.getPrice()?*?0.8);
        ????????????if?(orderBO.getWeight()?>?8)?{
        ????????????????throw?new?RuntimeException("超過物流最大重量");
        ????????????}
        ????????????orderBO.setRefundSupport(Boolean.TRUE);
        ????????}
        ????????//?C類型訂單
        ????????else?if?(OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType()))?{
        ????????????orderBO.setPrice(orderBO.getPrice()?*?0.7);
        ????????????if?(orderBO.getWeight()?>?7)?{
        ????????????????throw?new?RuntimeException("超過物流最大重量");
        ????????????}
        ????????????orderBO.setRefundSupport(Boolean.TRUE);
        ????????}
        ????????//?保存數(shù)據(jù)
        ????????OrderDO?orderDO?=?new?OrderDO();
        ????????BeanUtils.copyProperties(orderBO,?orderDO);
        ????????orderMapper.insert(orderDO);
        ????}
        }

        上述代碼從功能上完全可以實現(xiàn)業(yè)務需求,但是程序員不僅要滿足功能,還需要思考代碼的可維護性。如果新增一種訂單類型,或者新增一個訂單屬性處理邏輯,那么我們就要在上述邏輯中新增代碼,如果處理不慎就會影響原有邏輯。

        如何改變平鋪直敘的思考方式?這就要為問題分析加上縱向和橫向兩個維度,我選擇使用分析矩陣方法,其中縱向表示策略,橫向表示場景:



        3.4.1 縱向做隔離

        縱向維度表示策略,不同策略在邏輯上和業(yè)務上應該是隔離的,本實例包括優(yōu)惠策略、物流策略和退款策略,策略作為抽象,不同訂單類型去擴展這個抽象,策略模式非常適合這種場景。本文詳細分析優(yōu)惠策略,物流策略和退款策略同理。

        //?優(yōu)惠策略
        public?interface?DiscountStrategy?{
        ????public?void?discount(OrderBO?orderBO);
        }

        //?A類型優(yōu)惠策略
        @Component
        public?class?TypeADiscountStrategy?implements?DiscountStrategy?{

        ????@Override
        ????public?void?discount(OrderBO?orderBO)?{
        ????????orderBO.setPrice(orderBO.getPrice()?*?0.9);
        ????}
        }

        //?B類型優(yōu)惠策略
        @Component
        public?class?TypeBDiscountStrategy?implements?DiscountStrategy?{

        ????@Override
        ????public?void?discount(OrderBO?orderBO)?{
        ????????orderBO.setPrice(orderBO.getPrice()?*?0.8);
        ????}
        }

        //?C類型優(yōu)惠策略
        @Component
        public?class?TypeCDiscountStrategy?implements?DiscountStrategy?{

        ????@Override
        ????public?void?discount(OrderBO?orderBO)?{
        ????????orderBO.setPrice(orderBO.getPrice()?*?0.7);
        ????}
        }

        //?優(yōu)惠策略工廠
        @Component
        public?class?DiscountStrategyFactory?implements?InitializingBean?{
        ????private?Map?strategyMap?=?new?HashMap<>();

        ????@Resource
        ????private?TypeADiscountStrategy?typeADiscountStrategy;
        ????@Resource
        ????private?TypeBDiscountStrategy?typeBDiscountStrategy;
        ????@Resource
        ????private?TypeCDiscountStrategy?typeCDiscountStrategy;

        ????public?DiscountStrategy?getStrategy(String?type)?{
        ????????return?strategyMap.get(type);
        ????}

        ????@Override
        ????public?void?afterPropertiesSet()?throws?Exception?{
        ????????strategyMap.put(OrderTypeEnum.A_TYPE.getCode(),?typeADiscountStrategy);
        ????????strategyMap.put(OrderTypeEnum.B_TYPE.getCode(),?typeBDiscountStrategy);
        ????????strategyMap.put(OrderTypeEnum.C_TYPE.getCode(),?typeCDiscountStrategy);
        ????}
        }

        //?優(yōu)惠策略執(zhí)行
        @Component
        public?class?DiscountStrategyExecutor?{
        ????private?DiscountStrategyFactory?discountStrategyFactory;

        ????public?void?discount(OrderBO?orderBO)?{
        ????????DiscountStrategy?discountStrategy?=?discountStrategyFactory.getStrategy(orderBO.getType());
        ????????if?(null?==?discountStrategy)?{
        ????????????throw?new?RuntimeException("無優(yōu)惠策略");
        ????????}
        ????????discountStrategy.discount(orderBO);
        ????}
        }

        3.4.2 橫向做編排

        橫向維度表示場景,一種訂單類型在廣義上可以認為是一種業(yè)務場景,在場景中將獨立的策略進行串聯(lián),模板方法設(shè)計模式適用于這種場景。

        模板方法模式一般使用抽象類定義算法骨架,同時定義一些抽象方法,這些抽象方法延遲到子類實現(xiàn),這樣子類不僅遵守了算法骨架約定,也實現(xiàn)了自己的算法。既保證了規(guī)約也兼顧靈活性,這就是用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。

        //?創(chuàng)建訂單服務
        public?interface?CreateOrderService?{
        ????public?void?createOrder(OrderBO?orderBO);
        }

        //?抽象創(chuàng)建訂單流程
        public?abstract?class?AbstractCreateOrderFlow?{

        ????@Resource
        ????private?OrderMapper?orderMapper;

        ????public?void?createOrder(OrderBO?orderBO)?{
        ????????//?參數(shù)校驗
        ????????if?(null?==?orderBO)?{
        ????????????throw?new?RuntimeException("參數(shù)異常");
        ????????}
        ????????if?(OrderTypeEnum.isNotValid(orderBO.getType()))?{
        ????????????throw?new?RuntimeException("參數(shù)異常");
        ????????}
        ????????//?計算優(yōu)惠
        ????????discount(orderBO);
        ????????//?計算重量
        ????????weighing(orderBO);
        ????????//?退款支持
        ????????supportRefund(orderBO);
        ????????//?保存數(shù)據(jù)
        ????????OrderDO?orderDO?=?new?OrderDO();
        ????????BeanUtils.copyProperties(orderBO,?orderDO);
        ????????orderMapper.insert(orderDO);
        ????}

        ????public?abstract?void?discount(OrderBO?orderBO);

        ????public?abstract?void?weighing(OrderBO?orderBO);

        ????public?abstract?void?supportRefund(OrderBO?orderBO);
        }

        //?實現(xiàn)創(chuàng)建訂單流程
        @Service
        public?class?CreateOrderFlow?extends?AbstractCreateOrderFlow?{

        ????@Resource
        ????private?DiscountStrategyExecutor?discountStrategyExecutor;
        ????@Resource
        ????private?ExpressStrategyExecutor?expressStrategyExecutor;
        ????@Resource
        ????private?RefundStrategyExecutor?refundStrategyExecutor;

        ????@Override
        ????public?void?discount(OrderBO?orderBO)?{
        ????????discountStrategyExecutor.discount(orderBO);
        ????}

        ????@Override
        ????public?void?weighing(OrderBO?orderBO)?{
        ????????expressStrategyExecutor.weighing(orderBO);
        ????}

        ????@Override
        ????public?void?supportRefund(OrderBO?orderBO)?{
        ????????refundStrategyExecutor.supportRefund(orderBO);
        ????}
        }

        4 文章總結(jié)

        本文介紹了設(shè)計類型字段、設(shè)計擴展字段、設(shè)計業(yè)務二進制字段、設(shè)計類型入?yún)?、設(shè)計松散入?yún)?、設(shè)計接口版本號、縱橫做設(shè)計這七種增加系統(tǒng)可擴展性方法,希望本文對大家有所幫助。



        JAVA前線?


        歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實際應用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習

        瀏覽 23
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 成人性生交大片免费看2023年 | 女人被爽到呻吟娇喘视频免费 | 欧美乱伦视频 | 好湿好紧太硬了我太爽了软件 | 激情五月天综合 |