利用“充血枚舉類型”消除if/else

截取自:https://mp.weixin.qq.com/s/HEyPBwbQZVRbFL1ivruVbg

無處不在的 if else 牛皮癬

無論你的編程啟蒙語言是什么罐寨,最早學(xué)會的邏輯控制語句一定是 if else,但是不幸的是它在你開始真正的編程工作以后赖晶,會變成一個損害項目質(zhì)量的壞習(xí)慣膳沽。

幾乎所有的項目都存在 if else 泛濫的問題窒盐,但是卻沒有引起足夠重視警惕亏镰,甚至被很多程序員認(rèn)為是正常現(xiàn)象套么。

首先我來解釋一下為什么 if else 這個看上去人畜無害的東西是有害的培己、是需要嚴(yán)格管控的

  • if else if ...else 以及類似的 switch 控制語句,本質(zhì)上是一種 hard coding 硬編碼行為胚泌,如果你同意“magic number 魔法數(shù)字”是一種錯誤的編程習(xí)慣省咨,那么同理,if else 也是錯誤的 hard coding 編程風(fēng)格玷室;
  • hard coding 的問題在于當(dāng)需求發(fā)生改變時零蓉,需要到處去修改,很容易遺漏和出錯穷缤;
  • 以一段代碼為例來具體分析:
if ("3".equals(object.getString("type"))){
          String data = object.getString("data");
          Integer zmf = JSON.parseObject(data).getJSONObject("taobao_user_info").getInteger("zm_score");
          Map param = new HashMap();
          param.put("phoneNumber", object.getString("mobile"));
          List<Dperson> list1 = personBaseDaoI.find("from xyz where phoneNumber=:phoneNumber", param);
          if (list1 !=null){
              for (Dperson dperson:list1){
                  dperson.setZmScore(zmf);
                  personBaseDaoI.saveOrUpdate(dperson);
                  AppFlowUtil.updateAppUserInfo(dperson.getToken(),null,null,zmf);
              }
          }
}
  • if ("3".equals(object.getString("type")))

    • 顯然這里的"3"是一個 magic number敌蜂,沒人知道 3 是什么含義,只能推測津肛;
    • 但是僅僅將“3”重構(gòu)成常量 ABC_XYZ 并不會改善多少章喉,因為 if (ABC_XYZ.equals(object.getString("type"))) 仍然是面向過程的編程風(fēng)格,無法擴(kuò)展身坐;
    • 到處被引用的常量 ABC_XYZ 并沒有比到處被 hard coding 的 magic number 好多少秸脱,只不過有了含義而已;
    • 把常量升級成 Enum 枚舉類型呢部蛇,也沒有好多少摊唇,當(dāng)需要判斷的類型增加了或判斷的規(guī)則改變了,還是需要到處修改——Shotgun Surgery(霰彈式修改)
  • 并非所有的 if else 都有害涯鲁,比如上面示例中的 if (list1 !=null) { 就是無害的巷查,沒有必要去消除,也沒有消除它的可行性抹腿。判斷是否有害的依據(jù):

    • 如果 if 判斷的變量狀態(tài)只有兩種可能性(比如 boolean吮便、比如 null 判斷)時,是無傷大雅的幢踏;
    • 反之,如果 if 判斷的變量存在多種狀態(tài)许师,而且將來可能會增加新的狀態(tài)房蝉,那么這就是個問題;
    • switch 判斷語句無疑是有害的微渠,因為使用 switch 的地方往往存在很多種狀態(tài)搭幻。

充血枚舉類型——Rich Enum Type

正如前面分析呈現(xiàn)的那樣,對于代碼中廣泛存在的狀態(tài)逞盆、類型 if 條件判斷檀蹋,僅僅把被比較的值重構(gòu)成常量或 enum 枚舉類型并沒有太大改善——使用者仍然直接依賴具體的枚舉值或常量,而不是依賴一個抽象云芦。

于是解決方案就自然浮出水面了:在 enum 枚舉類型基礎(chǔ)上進(jìn)一步抽象封裝俯逾,得到一個所謂的“充血”的枚舉類型贸桶,代碼說話:

  • 實現(xiàn)多種系統(tǒng)通知機(jī)制,傳統(tǒng)做法:
enum NOTIFY_TYPE {    email,sms,wechat;  }  //先定義一個enum——一個只定義了值不包含任何行為的“貧血”的枚舉類型

if(type==NOTIFY_TYPE.email){ //if判斷類型 調(diào)用不同通知機(jī)制的實現(xiàn) 
    桌肴。皇筛。。
}else if (type=NOTIFY_TYPE.sms){
    坠七。水醋。。
}else{
    彪置。拄踪。。
}
  • 實現(xiàn)多種系統(tǒng)通知方式拳魁,充血枚舉類型——Rich Enum Type 模式:
enum NOTIFY_TYPE {    //1惶桐、定義一個包含通知實現(xiàn)機(jī)制的“充血”的枚舉類型
  email("郵件",NotifyMechanismInterface.byEmail()),
  sms("短信",NotifyMechanismInterface.bySms()),
  wechat("微信",NotifyMechanismInterface.byWechat());  
  
  String memo;
  NotifyMechanismInterface notifyMechanism;
  
  private NOTIFY_TYPE(String memo,NotifyMechanismInterface notifyMechanism){//2、私有構(gòu)造函數(shù)的猛,用于初始化枚舉值
      this.memo=memo;
      this.notifyMechanism=notifyMechanism;
  }
  //getters ...
}

public interface  NotifyMechanismInterface{ //3耀盗、定義通知機(jī)制的接口或抽象父類
    public boolean doNotify(String msg);
 
    public static NotifyMechanismInterface byEmail(){//3.1 返回一個定義了郵件通知機(jī)制的策的實現(xiàn)——一個匿名內(nèi)部類實例
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    }
    public static NotifyMechanismInterface bySms(){//3.2 定義短信通知機(jī)制的實現(xiàn)策略
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    } 
    public static NotifyMechanismInterface byWechat(){//3.3 定義微信通知機(jī)制的實現(xiàn)策略
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    }
}

//4、使用場景
NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);
  • 充血枚舉類型——Rich Enum Type 模式的優(yōu)勢:

    • 不難發(fā)現(xiàn)卦尊,這其實就是 enum 枚舉類型和 Strategy Pattern 策略模式的巧妙結(jié)合運用叛拷;
    • 當(dāng)需要增加新的通知方式時,只需在枚舉類 NOTIFY_TYPE 增加一個值岂却,同時在策略接口 NotifyMechanismInterface 中增加一個 by 方法返回對應(yīng)的策略實現(xiàn)忿薇;
    • 當(dāng)需要修改某個通知機(jī)制的實現(xiàn)細(xì)節(jié),只需修改 NotifyMechanismInterface 中對應(yīng)的策略實現(xiàn)躏哩;
    • 無論新增還是修改通知機(jī)制署浩,調(diào)用方完全不受影響,仍然是NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);
  • 與傳統(tǒng) Strategy Pattern 策略模式的比較優(yōu)勢:常見的策略模式也能消滅 if else 判斷扫尺,但是實現(xiàn)起來比較麻煩筋栋,需要開發(fā)更多的 class 和代碼量:

    • 每個策略實現(xiàn)需單獨定義成一個 class;
    • 還需要一個 Context 類來做初始化——用 Map 把類型與對應(yīng)的策略實現(xiàn)做映射正驻;
    • 使用時從 Context 獲取具體的策略弊攘;
  • Rich Enum Type 的進(jìn)一步的充血:

    • 上面的例子中的枚舉類型包含了行為,因此已經(jīng)算作充血模型了姑曙,但是還可以為其進(jìn)一步充血襟交;
    • 例如有些場景下,只是要對枚舉值做個簡單的計算獲得某種 flag 標(biāo)記伤靠,那就沒必要把計算邏輯抽象成 NotifyMechanismInterface 那樣的接口捣域,殺雞用了牛刀;
    • 這時就可以在枚舉類型中增加 static function 封裝簡單的計算邏輯;
  • 策略實現(xiàn)的進(jìn)一步抽象:

    • 當(dāng)各個策略實現(xiàn)(byEmail bySms byWechat)存在共性部分焕梅、重復(fù)邏輯時迹鹅,可以將其抽取成一個抽象父類;
    • 然后就像前一章節(jié)——業(yè)務(wù)模板 Pattern of NestedBusinessTemplate 那樣丘侠,在各個子類之間實現(xiàn)優(yōu)雅的邏輯分離和復(fù)用徒欣。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜗字,隨后出現(xiàn)的幾起案子打肝,更是在濱河造成了極大的恐慌,老刑警劉巖挪捕,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粗梭,死亡現(xiàn)場離奇詭異,居然都是意外死亡级零,警方通過查閱死者的電腦和手機(jī)断医,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奏纪,“玉大人鉴嗤,你說我怎么就攤上這事⌒虻鳎” “怎么了醉锅?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長发绢。 經(jīng)常有香客問我硬耍,道長,這世上最難降的妖魔是什么边酒? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任经柴,我火速辦了婚禮,結(jié)果婚禮上墩朦,老公的妹妹穿的比我還像新娘坯认。我一直安慰自己,他們只是感情好氓涣,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布鹃操。 她就那樣靜靜地躺著,像睡著了一般春哨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恩伺,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天赴背,我揣著相機(jī)與錄音,去河邊找鬼。 笑死凰荚,一個胖子當(dāng)著我的面吹牛燃观,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播便瑟,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼缆毁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了到涂?” 一聲冷哼從身側(cè)響起脊框,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎践啄,沒想到半個月后浇雹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡屿讽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年昭灵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伐谈。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡烂完,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诵棵,到底是詐尸還是另有隱情抠蚣,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布非春,位于F島的核電站柱徙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奇昙。R本人自食惡果不足惜护侮,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望储耐。 院中可真熱鬧羊初,春花似錦、人聲如沸什湘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闽撤。三九已至得哆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哟旗,已是汗流浹背贩据。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工栋操, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饱亮。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓矾芙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親近上。 傳聞我的和親對象是個殘疾皇子剔宪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359