Johnny's Collections

          生活總是有太多的無奈與失望,讓我們以在努力學(xué)習(xí)和工作中獲得的成就感和快樂來沖淡它們。

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            10 Posts :: 0 Stories :: 80 Comments :: 0 Trackbacks
          今天一個(gè)曾經(jīng)共事的同行問我:“要從編碼轉(zhuǎn)為設(shè)計(jì),大概需要多長(zhǎng)時(shí)間?”
          我的回答是:“編碼本身就是一種設(shè)計(jì),你可以設(shè)計(jì)你的代碼。”

          其實(shí)正如概要設(shè)計(jì)與詳細(xì)設(shè)計(jì),系統(tǒng)設(shè)計(jì)與架構(gòu)設(shè)計(jì)一樣,編碼與設(shè)計(jì)也是沒有明顯的邊界,每個(gè)正確成長(zhǎng)的程序員,都必須從編碼開始,慢慢鍛煉抽象思維、邏輯思維、面向?qū)ο笏季S,然后慢慢的過渡到系統(tǒng)設(shè)計(jì),再隨著經(jīng)驗(yàn)和知識(shí)的積累,慢慢過渡到架構(gòu)設(shè)計(jì)。下面我將會(huì)以最近的一個(gè)手頭的編碼任務(wù),簡(jiǎn)單介紹一下如何“設(shè)計(jì)”你的代碼。

          任務(wù)是這樣的,某銀行支付系統(tǒng)的客戶端接收銀行用戶錄入的轉(zhuǎn)賬數(shù)據(jù),當(dāng)轉(zhuǎn)賬數(shù)據(jù)被審批通過后,狀態(tài)轉(zhuǎn)變?yōu)?#8220;transfer”,同時(shí),該客戶端需要通過JMS以異步的方式向支付系統(tǒng)后臺(tái)發(fā)送一條帶有轉(zhuǎn)賬記錄(Instruction)的消息,后端在接收到信息之后,需要根據(jù)Instruction的一些相關(guān)信息,首先確定這筆轉(zhuǎn)賬數(shù)據(jù)是直接發(fā)送給真正進(jìn)行轉(zhuǎn)賬的清算(Clearing)銀行系統(tǒng),還是停留在后端系統(tǒng),等待后端系統(tǒng)中需要執(zhí)行的工作流程(work flow)。而后端系統(tǒng)需要對(duì)Instruction執(zhí)行的工作流程有兩個(gè),同時(shí)需要根據(jù)Instruction的一些相關(guān)信息進(jìn)行選擇。
          為了簡(jiǎn)化復(fù)雜度,我這里假設(shè)系統(tǒng)有一個(gè)InstructionHandleMsgDrivenBean,該bean有一個(gè)onMessage()方法,所有業(yè)務(wù)邏輯需要在該方法中實(shí)現(xiàn)。

          同時(shí)解釋一下詳細(xì)的業(yè)務(wù)細(xì)節(jié):
          • 判斷Instruction是否需要停留在后端等待執(zhí)行指定的工作流程有三個(gè)條件:xx、yy、zz,當(dāng)三個(gè)條件都為true時(shí),停留。
          • 判斷Instruction需要走A流程還是B流程,由4個(gè)因素的組合確定,如果用“Y”代表true,“N”代表false,那么由這個(gè)四個(gè)因素組成的“XXXX”一共有16種組合,不同的組合分別走A和B流程,如:YYNN、YYNY to A,NNYY、NNNY to B,……不累贅。
          好了,對(duì)于一個(gè)純編程人員來說,拿到這樣的需求,感覺邏輯很簡(jiǎn)單,可以直接編碼了,于是,他開始一行一行的編寫代碼(偽代碼):

          public void onMessage(InstructionInfo instructionInfo) {
              if(xx && yy && zz) { // 停留在后端等待執(zhí)行指定的工作流程
                  // 根據(jù)每種組合進(jìn)行條件判斷,走哪個(gè)流程
                  if(a==true && b==true && c==true && d==true {
                      ...
                  }
                  else if(...) {...}
                  else if(...) {...}
                  ...
                  else(...) {...}    
              }
          }

          這種做法是最為開發(fā)人員歡迎的,因?yàn)樗?jiǎn)單、直接,但這種做法也恰恰反映了開發(fā)人員的通病——使用Java編寫純面向過程的代碼。

          好了,說了一大堆,如何“設(shè)計(jì)”你的代碼呢?答案是:使用面向?qū)ο笏季S:

          我們拿到需求之后,可以分析,這個(gè)需求大體上分為兩部分:
          • 判斷是否需要停留在后端等待執(zhí)行指定的工作流程的部分
          • 選擇走哪個(gè)工作流程的部分

          有了這個(gè)前提,我可以設(shè)計(jì)出兩個(gè)職責(zé)單一的對(duì)象了:

          public class InstructionHandleDecisionMaker {
              public static boolean isHandledByBackEnd(InstructionInfo info) {
                  return (isXX(...) && isYY(...) && isZZ(...));
              }

              private booolean isXX(...) {
                  //TODO Implement the logic
                  return false;
              }
              private booolean isYY(...) {
                  //TODO Implement the logic
                  return false;
              }
              private booolean isZZ(...) {
                  //TODO Implement the logic
                  return false;
              }
          }

          public class InstructionWorkFlowSelector {
              private static Map mapping = new HashMap();
              static {
                  mapping.input("YYNN",WorkFlow.A);
                  mapping.input("NNYY",WorkFlow.B);
                  ...
              }

              public static WorkFlow getWorkFlow(Instruction info) {
                  StringBuilder result = new StringBuilder();
                  result.append(isA(...)).append(isB(...));
                  result.append(isC(...)).append(isD(...));
                  return mapping.get(result.toString());
              }
              private static String isA(...) {
                  //TODO Implment the logic
                  return "N";
              }
              private static String isB(...) {
                  //TODO Implment the logic
                  return "N";
              }
              private static String isC(...) {
                  //TODO Implment the logic
                  return "N";
              }
              private static String isD(...) {
                  //TODO Implment the logic
                  return "N";
              }
          }

          可以看到,我先按職責(zé)劃分了類,再按職責(zé)抽取了私有方法,“框架”設(shè)計(jì)好 ,為了讓編譯通過,我上面完整的填寫了代碼的,然后加上TODO標(biāo)識(shí),然后,我可以編寫我的onMessage方法了:

          public void onMessage(InstructionInfo instructionInfo) {
              if( InstructionHandleDecisionMaker.isHandledByBackEnd(...) ) {
                  WorkFlow wf =InstructionWorkFlowSelector.getWorkFlow(...);
                  //TODO Implment the logic
              }
          }

          到目前為止,我已經(jīng)用純面向?qū)ο蟮乃季S方式“設(shè)計(jì)”好我的代碼了,這時(shí),我思維非常清晰,因而代碼結(jié)構(gòu)也非常清晰,職責(zé)單一,內(nèi)聚高,耦合低,最后,我可以根據(jù)需求文檔的細(xì)節(jié)(沒有描述)慢慢的編寫我的實(shí)現(xiàn)了。

          復(fù)雜的事物總是由一些較簡(jiǎn)單的事物組成,而這些較簡(jiǎn)單的事物也是由更簡(jiǎn)單的事物組成,如此類推。因此,在編寫代碼的時(shí)候,先用面向?qū)ο蟮乃季S把復(fù)雜的問題分解,再進(jìn)一步分解,最后把簡(jiǎn)單的問題各個(gè)擊破,這就是一種設(shè)計(jì)。開發(fā)人員只要養(yǎng)成這種習(xí)慣,即使你每天都只是做最底層的編碼工作,其實(shí)你已經(jīng)在參與設(shè)計(jì)工作了,隨著知識(shí)和經(jīng)驗(yàn)的累積,慢慢的,你從設(shè)計(jì)代碼開始,上升為設(shè)計(jì)類、方法,進(jìn)而是設(shè)計(jì)模塊,進(jìn)而設(shè)計(jì)子系統(tǒng),進(jìn)而設(shè)計(jì)系統(tǒng)……,最終,一步一步成為一個(gè)優(yōu)秀的架構(gòu)師。

          最后,有一個(gè)真理奉獻(xiàn)給浮躁的程序員:

          優(yōu)秀的架構(gòu)師、設(shè)計(jì)師,必定是優(yōu)秀的程序員,不要因?yàn)槟愕穆毼簧仙?,就放棄編碼。

          補(bǔ)充說明:本博文純粹是討論一種思維習(xí)慣,不要把其做法生搬硬套,不管實(shí)際情況,直接在編碼的時(shí)候這樣做,不見得是最好的選擇。在實(shí)際編碼中,有如下問題你必須考慮:
          • 你需要考慮業(yè)務(wù)邏輯的可重用性和復(fù)雜程度,是否有必要設(shè)計(jì)出新的類或抽取新的私有方法來封裝邏輯,或者直接在原方法上編碼(如果足夠簡(jiǎn)單)。
          • 新的業(yè)務(wù)邏輯,是否在某些地方已經(jīng)存在,可以復(fù)用,即使不存在,這些邏輯是應(yīng)該封裝到新的類中,還是應(yīng)該放置到現(xiàn)有的類中,這需要進(jìn)行清晰的職責(zé)劃分。
          • 需要在設(shè)計(jì)和性能上作出權(quán)衡。
          • 如果在現(xiàn)成的系統(tǒng)中增加新的功能,而現(xiàn)成系統(tǒng)的編碼風(fēng)格與你想要的相差很遠(yuǎn),但你又沒有足夠的時(shí)間成本來進(jìn)行重構(gòu),那么還是應(yīng)該讓你的代碼與現(xiàn)成系統(tǒng)保持一致的風(fēng)格。

          posted on 2010-04-28 00:51 Johnny.Liang 閱讀(4963) 評(píng)論(8)  編輯  收藏 所屬分類: 編程技巧系統(tǒng)設(shè)計(jì)

          Feedback

          # re: “設(shè)計(jì)”你的代碼 2010-04-28 10:41 zcl
          很有啟發(fā),希望樓主能多寫這方面的文章,贊一個(gè)!  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼 2010-04-28 11:50 grass
          太精辟了。前幾天我還在網(wǎng)上發(fā)貼,被人罵的一踏糊涂,原來答案在樓主這里。  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼 2010-04-28 15:02 樂蜂網(wǎng)美麗俏佳人
          是空間上看簡(jiǎn)單的  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼[未登錄] 2010-04-29 00:42 onkyo
          小小的砸一下磚,大家探討一下:

          我比較質(zhì)疑以下這兩點(diǎn)
          “純面向?qū)ο蟮乃季S方式” 和 “內(nèi)聚高,耦合低”。

          和原來的代碼比較的話就是把原來集中在一起的代碼分散了。

          首先 InstructionHandleDecisionMaker 和 InstructionWorkFlowSelector 就不是面對(duì)對(duì)象的設(shè)計(jì), 用的是都是static函數(shù)。 實(shí)際上就是把原來代碼中的
          onMessage 中的代碼, 歸了一下類,拆成一些小函數(shù), 然后再插到InstructionHandleDecisionMaker 和 InstructionWorkFlowSelector 文件中去。 其實(shí)際上就是

          public void onMessage(InstructionInfo instructionInfo) {
          if(isHandledByBackEnd(...) ) {
          WorkFlow wf =getWorkFlow(...);
          //TODO Implment the logic
          }
          }

          private static Map mapping = new HashMap();
          static {
          mapping.input("YYNN",WorkFlow.A);
          mapping.input("NNYY",WorkFlow.B);
          ...
          }

          private WorkFlow getWorkFlow(Instruction info) {
          ...
          }

          private String isA(...) {}
          private String isB(...) {}
          private String isC(...) {}
          private String isD(...) {}

          不能繼承,不能重用。

          其次代碼是高耦合的, 當(dāng)流程的判斷條件變更的話是需要修改代碼的,因?yàn)榕袛鄺l件是寫死在代碼里面的。 (當(dāng)然這就是為什么需要工作流框架的原因)  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼[未登錄] 2010-04-29 00:54 onkyo
          個(gè)人覺得比較好的方案是聲明一個(gè)interface

          public interface WorkflowFactroy {
          Workflow create(Instruction info)
          }

          把邏輯寫在WorkflowFactoryImpl里面, 用ioc注入WorkflowFactoryImpl.  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼 2010-04-29 09:40 Johnny.Liang
          @onkyo
          呵呵,回復(fù)一下這為同學(xué)的兩個(gè)評(píng)論,首先,你說得對(duì),static就不是面向?qū)ο?,純面向?qū)ο笫菦]有static函數(shù)的,但我要解釋兩點(diǎn),上面的代碼純屬演示如何改變一種思維方式,我并沒有過于斟酌于代碼的細(xì)節(jié),如果要純面向?qū)ο蟮脑?,我可以把static聲明為對(duì)象方法,然后讓這個(gè)類變成Singleton;其次,如果所有東西都要考慮繼承的話,就是過度設(shè)計(jì)對(duì)了,正如我在本博文的最后的特別說明,設(shè)計(jì)是要針對(duì)需求的,假如我這個(gè)流程相當(dāng)穩(wěn)定,不存在多態(tài)的情況,那么我就(至少在目前)不需要過度的把它設(shè)計(jì)為接口,然后再提供實(shí)現(xiàn)類,再通過依賴注入,而關(guān)于你提到的private方法不能繼承和重用,這也是一個(gè)好問題,假如根據(jù)實(shí)際情況,我不希望我的類或方法被繼承或重寫,我就需要聲明其為final/private了,君不見JDK的很多類都是final的嗎?這同樣也回答了你第二個(gè)評(píng)論的問題,沒需要多態(tài),或沒需求切換實(shí)現(xiàn),就沒必要接口。

          總之,謝謝你的發(fā)言,我只能強(qiáng)調(diào),上面的代碼純屬表達(dá)一種思維方式,況且,不考慮現(xiàn)實(shí)環(huán)境和實(shí)際需求,孤立的去討論一個(gè)類是否有接口,一個(gè)方法是否需求繼承,一個(gè)靜態(tài)方法是否必須設(shè)計(jì)為對(duì)象方法,都是沒有實(shí)際意義的,搞不好就是一種“過度設(shè)計(jì)”。  回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼[未登錄] 2010-04-30 16:39 onkyo
          @Johnny.Liang
          我只是就博文中我不太認(rèn)同的地方發(fā)表一下看法,大家探討一下相互提高。

          首先我非常同意 設(shè)計(jì)是要針對(duì)需求的 的這句話, 這個(gè)流程相當(dāng)穩(wěn)定,不存在多態(tài)的情況,那么第一種寫法

          public void onMessage(InstructionInfo instructionInfo) {
          if(xx && yy && zz) { // 停留在后端等待執(zhí)行指定的工作流程
          // 根據(jù)每種組合進(jìn)行條件判斷,走哪個(gè)流程
          if(a==true && b==true && c==true && d==true {
          ...
          }
          else if(...) {...}
          else if(...) {...}
          ...
          else(...) {...}
          }
          }

          我覺的完全可行。 何必再拆分出兩個(gè)類? 還便于閱讀,便于修改。 因?yàn)檫壿嫸技性谝黄鹆恕?這就是面對(duì)過程的設(shè)計(jì), 非常的合理。

          正如博文題目設(shè)計(jì)你的代碼: 每個(gè)正確成長(zhǎng)的程序員,都必須從編碼開始,慢慢鍛煉抽象思維、邏輯思維、面向?qū)ο笏季S,然后慢慢的過渡到系統(tǒng)設(shè)計(jì),再隨著經(jīng)驗(yàn)和知識(shí)的積累,慢慢過渡到架構(gòu)設(shè)計(jì)。

          既然我們要抽象上述的代碼, 要使用面對(duì)對(duì)象思維,要重構(gòu)上面的代碼, 就應(yīng)該搞清楚為什么要用抽象,為什么要面對(duì)對(duì)象思維。 抽象和面對(duì)對(duì)象編程的目的無非是最大限度的重用。 那么就應(yīng)該面對(duì)接口編程, 解耦關(guān)系。

          我的觀點(diǎn)就是既然要設(shè)計(jì),就要好好設(shè)計(jì)。 如果要用省事的方法,那就用最省事的方法。

            回復(fù)  更多評(píng)論
            

          # re: “設(shè)計(jì)”你的代碼 2010-04-30 17:50 Johnny.Liang
          @onkyo
          明白你的意思,謝謝你的意見,我往后會(huì)發(fā)表一些針對(duì)如何使用面向?qū)ο笏季S進(jìn)行設(shè)計(jì),及其真正的好處的博文,當(dāng)中就會(huì)詳細(xì)的說明使用面向?qū)ο笏季S在某些場(chǎng)景中的好處。  回復(fù)  更多評(píng)論
            


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 枣阳市| 溧水县| 藁城市| 儋州市| 嘉兴市| 格尔木市| 西藏| 肥乡县| 水富县| 时尚| 宝丰县| 辉县市| 华容县| 沈丘县| 治县。| 炎陵县| 淮阳县| 虞城县| 大庆市| 太仆寺旗| 五莲县| 平和县| 吉安市| 清水河县| 通州区| 曲麻莱县| 杭锦旗| 米脂县| 重庆市| 民县| 太白县| 广昌县| 嵩明县| 封丘县| 永登县| 烟台市| 日喀则市| 家居| 靖安县| 竹山县| 宾阳县|