海鷗航際

          JAVA站
          posts - 11, comments - 53, trackbacks - 1, articles - 102

          Domain Model Design

          Posted on 2005-01-12 20:23 海天一鷗 閱讀(866) 評論(0)  編輯  收藏 所屬分類: J2EE

          一直想系統(tǒng)的整理一下自己有關(guān)Domain Model實踐的嘗試。但總覺得自己的想法還不夠系統(tǒng)而作罷。
          然而從另一方面看“系統(tǒng)的東西”也許永遠做不到,失去了目標(biāo)的生活該會多乏味。
          因此我決定將自己有關(guān)Domain Model設(shè)計的有關(guān)實踐和思考和盤托出,也算是拋磚引玉。歡迎大家
          參與討論,遇到同你的觀點相左的地方,希望能以包容的態(tài)度來面對,我們是朝同一方向走的伙伴而不是
          相互對視的敵人。:)

          在深入討論之前我先拋出一些原則和概念,最后你會看到這些概念和原則的威力。
          1.按照概念依賴的原則來組織業(yè)務(wù)層。
          2.將業(yè)務(wù)活動(業(yè)務(wù)流程)建模成類。
          3.用業(yè)務(wù)活動(業(yè)務(wù)流程)作為關(guān)聯(lián)整個業(yè)務(wù)層各種對象的骨架。
          4.在業(yè)務(wù)活動中鑿出擴展點,使用不同接口分離不同性質(zhì)業(yè)務(wù)對象。
          5.將對象的存儲理解為業(yè)務(wù)層概念。
          ......

          概念依賴

          這是我認(rèn)為能否得到良好業(yè)務(wù)層最重要的概念。
          在我系統(tǒng)框架設(shè)計將要完成,開始涉及業(yè)務(wù)層設(shè)計時,我腦袋一片空白,書上,大家討論的大多是整個系統(tǒng)的結(jié)構(gòu)從UI層
          到服務(wù)層到數(shù)據(jù)訪問層到數(shù)據(jù)庫。到底業(yè)務(wù)層該如何組織?Martin Fowler的POEAA的書中沒有回答。找到的相關(guān)
          書籍也都過于空泛。Martin Fowler的分析模式有些用處,但不夠系統(tǒng)。透過Martin fowler網(wǎng)站,我拿到了
          Domain Driven Design的發(fā)行前版本。該書給了我很大的啟示。其中的要點有:
          關(guān)于關(guān)聯(lián):
          1.Imposing a traversal direction (強制一個關(guān)聯(lián)的導(dǎo)航方向)
          ......
          關(guān)于Responsibility Layers(業(yè)務(wù)職責(zé)層)的劃分:
          作者給出了三個指導(dǎo)原則:Conceptual dependency.(概念依賴)為其中一項。
          書中給出的描述的是業(yè)務(wù)職責(zé)層上層的對象需要通過下層對象才能在概念上完整,
          相反下層對象則可獨立于上層對象存在含義。這樣天然的下層對象相對于上層對象
          會更穩(wěn)定。并且在今后演變的過程中,使同擴展的方式來完善系統(tǒng),而不是改變對象
          的方式。
          通過實踐,我覺得這條原則可以應(yīng)用在任何兩個有關(guān)聯(lián)的業(yè)務(wù)對象上。通常可以通過
          概念依賴先建立一個導(dǎo)航方向。這能夠滿足大多數(shù)的需求。當(dāng)確實需要反向?qū)Ш綍r,
          只要理由充分可以隨時加上,并且如果先前將這兩個對象放入不同包中,這時需要
          將他們合并到同一個包中。
          我見過一個不好的設(shè)計。Customer具有很多Flag分別標(biāo)記該客戶是否掛失,凍結(jié),注銷等等。
          通常叫做客戶狀態(tài),然而這是不對的,這違背了單一職責(zé)原則。事實上除了注銷外
          掛失和凍結(jié)都不應(yīng)該算作Customer的本質(zhì)屬性。相反我把他們看作某種約束,進而把掛失看作
          一種協(xié)議.....因為Customer的概念可以不依賴于掛失和凍結(jié)的概念,相反掛失和凍結(jié)卻要依賴
          Customer的概念,應(yīng)為這是他們動作的主體。
          同樣的一開始就讓Customer有GetAccount的方法同樣不好。因為Customer的概念確實不依賴Account
          XXXAccount卻可以有Customer的屬性,Account在概念上依賴Customer。
          按照概念依賴原則我們能更好的理解業(yè)務(wù)職責(zé)層的劃分。DDD中建議了如下的職責(zé)層。
          按從高到低分別為:

          依賴方向
          | Decision
          | Policy
          | Commitment
          | Operation
          V Potential

          Potential中包括類似Customer,Employee等Party方面的類。對應(yīng)支持業(yè)務(wù)。
          Operation中包括了核心業(yè)務(wù)如存取款,買賣以及同這些業(yè)務(wù)關(guān)聯(lián)的Account,Product等等。
          Commmitment對于客戶包括同客戶簽訂的協(xié)議等。對于員工來說包括授權(quán)等。
          Policy包括計算某種收費的策略,比如電信收費的算法。對應(yīng)支持業(yè)務(wù)。
          Decision包括主要對應(yīng)于管理業(yè)務(wù)。監(jiān)控系統(tǒng)多在這層。
          從上到下觀察,很好的遵循了概念依賴的原則。
          從另一方面來看,可以根據(jù)概念隨著時間發(fā)展的順序來建立對象之間的關(guān)系。這樣會天然的滿足概念依賴原則。
          后面發(fā)展起來的概念可以依賴前面的已經(jīng)存在的概念,而反過來這不可。這是系統(tǒng)穩(wěn)定的關(guān)鍵。
          同客戶簽訂的各種協(xié)議可以不斷的發(fā)展,但是客戶對象是穩(wěn)定的。
          同理收費的策略可以變化但是最終反映到帳戶上的都只是對Balance的變更。所以收費策略比
          帳戶更不穩(wěn)定。
          客戶對象也要比帳戶對象穩(wěn)定。

          從按照穩(wěn)定的方向依賴的原則出發(fā),我們可以得到對象間的單向依賴。當(dāng)然也會存在雙向關(guān)聯(lián)
          的對象,然而這種情況在我的實踐中不是很多。而且一旦你懂得了單向關(guān)聯(lián)的好處后,你就會
          謹(jǐn)慎的使用雙向關(guān)聯(lián)。濫用關(guān)聯(lián)會使得整個業(yè)務(wù)層象DDD中說的,變成一大塊“果凍”,你隨便觸動
          果凍某一塊,整個果凍都會顫動。
          同樣為了簡化設(shè)計,對象的關(guān)系中多對多的關(guān)系盡量避免。如果可以
          則通過限定角色轉(zhuǎn)化為一對多或一對一的關(guān)系。

          以上是關(guān)于概念依賴的觀念,下面讓我們看看如何建模業(yè)務(wù)中的活動。
          有一種做法是使用分析模型中的控制類直接映射到設(shè)計中類中。我看來這不是好的做法。
          這里談?wù)劮治雠c設(shè)計的區(qū)別。
          從分析的角度來看,業(yè)務(wù)實體總是被動的。業(yè)務(wù)是通過控制對象操作業(yè)務(wù)實體來完成的。
          分析時我們是關(guān)注是什么問題。這要求我們客觀的來描述現(xiàn)實。
          進入設(shè)計階段我們關(guān)注的是如何解決的問題。控制對象施加與業(yè)務(wù)實體的操作加入不涉及
          第三者,則這個操作可以并入被操作的實體類中。然而分析中控制對象的概念是如此的
          深刻,以至于只涉及Customer的ChangePassword方法該放到哪里都成了問題。類不是
          “某概念 + 所關(guān)心該概念的屬性 + 最終施加與這些屬性上的操作” 的封裝,又是什么呢?
          下面的問題是如何建模跨越多個業(yè)務(wù)實體的操作?
          舉個例子:銀行開戶。
          現(xiàn)在假設(shè)開戶時涉及到下面的一些操作對象。
          創(chuàng)建一個Customer對象。
          創(chuàng)建一個CapitalAccount對象。
          存入一定數(shù)額的現(xiàn)金。
          記錄一筆開戶流水。
          整個業(yè)務(wù)活動,我可以建模為OpenCustomerAct對象。偽碼如下:

          public class OpenCustomerAct extends CustomerAct
          {
          ...
          public void override doRun()
          {
          Customer customer = Customer.create(...);
          CapitalAccount capitalAccount = CapitalAccount.create(customer,...);
          capitalAccount.deposit(...);
          OpenCustomerLog.create(this);
          }
          ...
          }

          所需的參數(shù)通過構(gòu)造函數(shù)得到。
          將所有的業(yè)務(wù)活動都建模成一個Act,這非常重要。甚至你可以在Session中放入一個Act來
          表示當(dāng)前正在進行的業(yè)務(wù)。所有的擴展都是從Act開始的。
          假如你要對Act施加某種檢查,那么對doRun方法進行攔截可以達到該目的。
          用例能夠簡化到只剩下流程,同樣道理Act也可以做到這點。
          對于象RichClient的交互模式,通常只在最后才提交業(yè)務(wù),中間的交互都是在準(zhǔn)備提交的數(shù)據(jù)。
          那么在中間調(diào)用的方法中可以只new XXXAct而不執(zhí)行doRun操作。這樣做是因為中間的調(diào)用
          可能會用到XXXAct來作為上下文。現(xiàn)在我還沒有想好在這樣的中間過程中,如何能夠觸發(fā)
          植入到donRun前的檢查?或許可以創(chuàng)建一個空doRun的子類覆蓋掉父類實際的操作?

          Act

          public interface Act
          {
          Operator getOperator();//誰
          Date getOccurDate();//在什么時間
          String getOccurPlace();//什么地點
          BusinessType getBusinessType();//做什么業(yè)務(wù)
          ActState getActState();//業(yè)務(wù)運行的當(dāng)前狀態(tài)
          }
          “誰在什么時間什么地點做了什么業(yè)務(wù)。”
          這描述了任何業(yè)務(wù)的基本方面。從哲學(xué)的角度來看,“我們得到了Act,我們就得到了事物的基礎(chǔ)”。
          當(dāng)我們具體的描述某項業(yè)務(wù)時,假如需要向調(diào)用方暴露特定的屬性。
          我們可以隨時添加到Act的子接口中。
          例如同Customer相關(guān)的Act可定義為:
          public interface CustomerAct extends Act
          {
          Cutomer getCustomer();//針對哪個客戶
          }
          在復(fù)雜一點的情況下,如業(yè)務(wù)需要多人協(xié)作完成,可以通過組合模式達到目的。

          public interface CompositeAct extends Act
          {
          Act[] getActs();
          }
          涉及到一段時間有中間狀態(tài)的工作流也應(yīng)該可以作為Act的子接口進行擴展。
          不過我沒有做過這方面的嘗試。

          將Act放入Session

          將Act放入Session使得可以方便得到業(yè)務(wù)運行的上下文。而且通過擴展Act。
          可以從Act或其子接口中得到想得到的任何東西,這使得任何擴展都成為可能。

          這里說明一下Act類的位置應(yīng)當(dāng)放入Potential層中,并且與Operator在一起。
          因為Potential層的業(yè)務(wù)對象也需要業(yè)務(wù)活動來維護。
          如果你的框架中Sesion在更基礎(chǔ)的包中,則可以給Act提供一個空內(nèi)容的父接口,放入Session所在的包中。
          public interface AbstractAct
          {
          }

          public interface Act extends AbstractAct
          {
          ...
          }
          Session提供得到AbstractAct的入口。
          public class Session
          {
          ...
          static public AbstractAct getAbstractAct()
          {
          return Instance().abstractAct;
          }
          ...
          }

          Act上的擴展點

          按照分層的觀點,下層不允許依賴上層,然而業(yè)務(wù)對象卻是協(xié)作完成某個目的的。
          而且只要業(yè)務(wù)對象需要維護,就需要相關(guān)的Act。
          例如:銀行中的存錢業(yè)務(wù),參考上面的分層,我們把它放入Operation層。
          在存錢的業(yè)務(wù)中,我們需要檢查該客戶是否做了掛失。而掛失協(xié)議我們是放在Commitment層。
          顯然,Operation層不能直接調(diào)用Commitment層的協(xié)議。
          DIP模式發(fā)話了“用我”。
          在Operation層中定義Commitment層接口,和一個工廠,使用反射實現(xiàn)這種調(diào)用。在Act中調(diào)用。
          abstract public class ActImpl
          extends abstractActImpl
          implements Act
          {
          public virtual void run()
          {
          doPreprocess();
          doRun();
          doPostprocess();
          }
          abstract public doPreprocess();
          abstract public doRun();
          abstract public doPostprocess();
          }

          public interface CustomerCommitment
          {
          void affirmCanDo();
          }

          abstract public class CustomerActImpl
          extends ActImpl
          implements CustomerAct
          {
          ...
          public override void doPreprocess()
          {
          ...
          //擴展點
          CustomerCommitment customerCommitment = CustomerCommitmentFactory.create(this);
          customerCommitment.affirmCanDo();
          ...
          }
          ...
          }

          public interface InnerCustomerCommitment
          {
          void affirmCanDo(CustomerAct customerAct);
          }

          public class CustomerCommitmentImpl implements CustomerCommitment
          {
          private CustomerAct customerAct;

          public CustomerCommitmentImpl(CustomerAct customerAct)
          {
          this.customerAct = customerAct;
          }

          public void affirmCanDo()
          {
          ...
          //通過配置得到該customerAct對應(yīng)需要檢查的客戶約束,包括協(xié)議,逐一檢查。
          DomainObjectCollection commitmentTypes = CustomerCommimentRepository.findByBusinessType(customerAct.getBusinessType());

          ...
          foreach( CommitmentType typeItem in commitmentTypes )
          {
          InnerCustomerCommitment commitment = getCommitment(typeItem);
          commitmentItem.affirmCanDo(customerAct);
          }
          ...
          }
          }

          public class CustomerLostReportAgreementChecker implements InnerCustomerCommitment
          {
          public void affirmCanDo(CustomerAct customerAct)
          {
          Check.require(customerAct.getCustomer() != null,"客戶不存在");

          CustomerLostReportAgreement customerLostReportAgreement =
          CustomerLostReportAgreementRepository.find(customerAct.getCustomer());

          if(customerLostReportAgreement != null)
          {
          agreement.affirmCanDo(customerAct);
          }

          }
          }

          public class CustomerLostReportAgreement
          {
          ...
          public void AffirmCanDo(CustomerAct customerAct)
          {
          if(customerAct.getOccurDate <= expiringDate)
          throw new CustomerLossReportedException(customer);
          }
          ...
          }

          同樣道理,可以對其他上層的對象使用DIP使依賴倒置。
          比如:電信計算費用。就可以通過在CustomerAct的doRun中插入擴展點來實現(xiàn)。
          這樣復(fù)雜的計費算法就被封裝在接口之后了。可以分配另外的人員來開發(fā)。
          業(yè)務(wù)活動的流程仍然清晰可見。
          是啊,這正是接口的威力,大多數(shù)的設(shè)計模式不也是基于這種原理嗎?

          還有在Act上的擴展點可以分為兩類,顯式的和隱式的。
          電信費用的計算就是顯式的,因為CustomerAct需要知道計算的結(jié)果,用來從帳戶中扣除金額。
          而檢查掛失協(xié)議是隱式的,CustomerAct可以對此一無所知。

          通過在Act上的擴展點,我們可以向上擴展。
          這仿佛是在樹枝上種木耳,呵呵。

          DIP VS Facade

          對于上面的情況,另外一種方法是使用Facade。
          讓我們比較一下兩者。
          簡要說明一下Facade的做法:
          abstract public class CustomerActImpl
          extends ActImpl
          implements CustomerAct
          {
          ...
          public override void doPreprocess()
          {
          ...
          //注意:這里傳遞的參數(shù),會使得用Facade方式的人大傷腦筋。
          //按照掛失的要求目前傳遞getBusinessType(),getCustomer(),getOccurDate()就夠了
          //但是對于所有的CustomerCommitment這些參數(shù)就不一定夠了。
          //比如:客戶可能簽訂指定員工協(xié)議。(指只允許協(xié)議中指明的員工能操作的業(yè)務(wù))
          //那么該接口需要添加getOperator()參數(shù)。
          //接口變得不穩(wěn)定。
          CustomerCommitmentManager.affirmCanDo(getBusinessType(),getCustomer(),getOccurDate(),?,...);
          ...
          }
          ...
          }

          Facade可以使得在Act中也是只提供一個調(diào)用點,但是因為不是依賴倒置的關(guān)系,不得不顯示的說明需要用到的參數(shù)。
          相反使用DIP模式,接口中定義的是Act的接口,而Act是可以擴展的。(是否擴展全部看上層的對象是否需要)。
          而正是因為相應(yīng)的CustomerCommitment總是處于需要檢查的XXXAct的上層。這樣具體的CustomerCommitment
          總是可以依賴XXXAct。因此可以獲得任何想要得到的信息。

          同樣對于電信計算費用的例子,因為傳遞的參數(shù)是CustomerAct接口。所以對于今后任何可能的擴展該接口都是不會變化的。
          能夠做到這一點,完全要歸功于將計算費用放入Operation的上層Policy中,你能體會到其中的要領(lǐng)嗎?

          形象一點來說,使用DIP模式,采取的是一種專家模式。
          DIP的Act說的是:“CustomerCommitment你看看我現(xiàn)在的情況,還能運行嗎?”
          相反Facade模式,則是令人厭煩的嘮叨模式。
          Facade的Act說的是:“CustomerCommitment,現(xiàn)在執(zhí)行的客戶是XXX,業(yè)務(wù)是XXX,時間是XXX,...你能告訴我還能運行下去嗎?”
          顯然DIP要瀟灑得多。

          實現(xiàn)接口 VS 繼承父類  

          這里稍稍偏離一下主題,討論一下接口同繼承的問題。
          什么時候使用接口?什么時候使用繼承?
          這似乎是個感覺和經(jīng)驗問題。或者我們會傾向于多使用接口,少使用繼承。
          可不可以再進一步呢?
          以下是我的觀點:

          “接口是調(diào)用方要求的結(jié)果,而繼承則是實現(xiàn)方思考的產(chǎn)物。”

          畢竟如果我們定義的接口沒有被用到,那它就沒有任何用處。
          接口的目的在于制定虛的標(biāo)準(zhǔn),從而使調(diào)用方不依賴于實現(xiàn)方。
          而繼承某個父類則多半是基于“偷懶“的考慮,已經(jīng)存在的東西,我為什么不利用一下?
          當(dāng)然這樣說是忽略了繼承的真正用意--單點維護。

          所以在定義XXXAct的接口時,需要多考慮一下,上層對象需要Act中的提供什么特性,會如何使用它。

          接口屬于調(diào)用方。

          業(yè)務(wù)對象的持久化

          一個會引起爭議的問題,是業(yè)務(wù)層是否會涉及業(yè)務(wù)對象持久化的概念。
          答案是肯定的。
          DDD中在描述The life cycle of a domain object時,給出了兩種形式的持久化。
          Store和Archive。我們使用的較多是Store。

          但是這不代表業(yè)務(wù)層要依賴數(shù)據(jù)訪問層。相反依賴關(guān)系應(yīng)該倒過來。數(shù)據(jù)訪問層依賴
          業(yè)務(wù)層。通常我們使用Mapper實現(xiàn),在hibernate中通過配置達到該目的。
          要做到業(yè)務(wù)層不依賴于數(shù)據(jù)訪問層,同樣借助接口來完成。
          在業(yè)務(wù)層定義數(shù)據(jù)訪問的接口,為了方便,可以使用一個類來封裝這些操作。

          public interface CustomerFinder
          {
          Customer findByID(ID id);
          Customer findByCode(String code);
          DomainObjectCollection findByName(String name);
          ...
          }

          public class CustomerRepository
          {
          private static CustomerFinder finder = null;
          private static CustomerFinder getFinderInstance()
          {
          if (finder == null)
          {
          finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder");
          }
          return finder;
          }

          public static Customer findByID(ID id)
          {
          Customer obj = getFinderInstance().findByID(id);
          Check.require(obj != null,
          "未找到ID為:" + id.toString() +
          "對應(yīng)的 Customer。");
          return obj;
          }
          ...
          }

          在數(shù)據(jù)訪問層實現(xiàn)這些接口。因為是數(shù)據(jù)訪問層依賴業(yè)務(wù)層,所以你可以采用多種技術(shù)來實現(xiàn),
          使用hibernate這樣的開源項目,或者手工編寫Mapper。

          ID id

          另外一個有爭議的問題是Domain層是否要引入與業(yè)務(wù)無關(guān)的ID來標(biāo)識不同的對象呢?
          我的經(jīng)驗是在業(yè)務(wù)層引入ID的概念會使很多事情變得方便些。
          如:Lazyload。
          這是否不屬于業(yè)務(wù)的范疇?是在概念上不屬于業(yè)務(wù)。但在業(yè)務(wù)上
          不是沒有對應(yīng)的概念。
          例如:保存客戶定購信息的訂單,作為標(biāo)識的就是訂單號,這是給人使用的。
          在使用電腦后,我們也給對象一個它能理解的統(tǒng)一標(biāo)識,這就是ID。
          另外不要使用業(yè)務(wù)上的概念作為主鍵和外鍵,因為它們本來就不是數(shù)據(jù)庫的概念。
          否則,會使得業(yè)務(wù)概念同數(shù)據(jù)庫的概念混淆起來。

          ID的使用通常會選擇效率較高的long類型。
          不過我們的實現(xiàn)走得更遠,我們將其封裝為ID對象。

          Service層

          現(xiàn)在我們向上看看將業(yè)務(wù)層包裹的服務(wù)層。
          服務(wù)層是架設(shè)在應(yīng)用層和業(yè)務(wù)層的橋梁,用來封裝對業(yè)務(wù)層的訪問,因此
          可以把服務(wù)層看作中介,充當(dāng)兩個角色:
          1.實現(xiàn)應(yīng)用層接口要求的接口;
          2.作為業(yè)務(wù)層的外觀。
          服務(wù)層的典型調(diào)用如下:

          public interface CustomerServices
          {
          void openCustomer(CustomerInfo cutomerInfo);
          void customerLostReport(String customerCode,Date expiringDate,String remark);
          CutomerBasicInfo getCutomerBasicInfo(String customerCode);
          ...
          }

          public class CustomerServicesImpl
          extends ServiceFacade
          implements CustomerServices
          {
          ...
          public void openCustomer(CustomerInfo cutomerInfo)
          {
          try
          {
          init();

          OpenCustomerAct openCustomerAct =
          new OpenCustomerAct(customerInfo.name,
          customerInfo.code,
          customerInfo.address,
          customerInfo.plainpassword
          ...
          );
          openCustomerAct.run();

          commit();
          }
          catch(Exception e)
          {
          throw ExceptionPostprocess(e);
          }
          }

          public void customerLostReport(String customerCode,Date expiringDate,String remark)
          {
          try
          {
          Check.require(customerCode != null && customerCode != "",
          "無效的客戶代碼:" + customerCode);
          init();

          CustomerLostReportAct customerLostReportAct =
          new CustomerLostReportAct(customerCode,
          expiringDate,
          remark);
          customerLostReportAct.run();

          commit();
          }
          catch(Exception e)
          {
          throw ExceptionPostprocess(e);
          }
          }

          public CutomerBasicInfo getCutomerBasicInfo(String customerCode)
          {
          try
          {
          Check.require(customerCode != null && customerCode != "",
          "無效的客戶代碼:" + customerCode);
          init();
          Customer customer = CustomerRepository.findByCode(customerCode);

          //這里選擇的是在CustomerRepository外拋出CustomerNotFoundException異常,
          //另一種方法是在CustomerRepository中拋出CustomerNotFoundException異常。
          //因為CustomerRepository在于通過客戶代碼查找對應(yīng)的客戶。至于是否應(yīng)該拋出
          //異常則交給業(yè)務(wù)層或服務(wù)層來處理。
          //這里有很微妙的區(qū)別,拋出CustomerNotFoundException應(yīng)該是誰的職責(zé)呢?
          //你的想法是什么?Smile
          if(customer == null)
          throw new CustomerNotFoundException(customerCode);

          CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer);
          return cutomerBasicInfo;
          }
          catch(Exception e)
          {
          throw ExceptionPostprocess(e);
          }
          }

          ...
          }

          服務(wù)層的代碼很簡單,不是嗎?

          上面的代碼可以通過AOP進一步的簡化。使用AOP實現(xiàn)我希望代碼象下面這樣簡單。
          public class CustomerServicesImpl
          implements CustomerServices
          {
          ...
          public void openCustomer(CustomerInfo cutomerInfo)
          {
          OpenCustomerAct openCustomerAct =
          new OpenCustomerAct(customerInfo.name,
          customerInfo.code,
          customerInfo.address,
          customerInfo.plainpassword
          ...
          );
          openCustomerAct.run();
          }

          public void customerLostReport(String customerCode,Date expiringDate,String remark)
          {
          Check.require(customerCode != null && customerCode != "",
          "無效的客戶代碼:" + customerCode);
          CustomerLostReportAct customerLostReportAct =
          new CustomerLostReportAct(customerCode,
          expiringDate,
          remark);
          customerLostReportAct.run();
          }

          public CutomerBasicInfo getCutomerBasicInfo(String customerCode)
          {
          Customer customer = CustomerRepository.findByCode(customerCode);
          if(customer == null)
          throw new CustomerNotFoundException(customerCode);

          CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer);
          return cutomerBasicInfo;
          }

          DTO or Not

          我認(rèn)為是否使用DTO取決于項目的大小,開發(fā)團隊的結(jié)構(gòu),以及對項目演變預(yù)期的評估結(jié)果。
          不使用DTO而直接使用PO傳遞到應(yīng)用層適用于一個人同時負責(zé)應(yīng)用層和業(yè)務(wù)層的短期簡單項目;
          一旦采用該模式作為構(gòu)架,我不知道業(yè)務(wù)層是否還能叫做面向?qū)ο蟆?
          原因如下:
          1.使用PO承擔(dān)DTO的職責(zé)傳遞到應(yīng)用層,迫使PO不能包含業(yè)務(wù)邏輯,這樣業(yè)務(wù)邏輯會暴露給應(yīng)用層。
          業(yè)務(wù)邏輯將由類似于XXXManager的類承擔(dān),這樣看來似乎PO有了更多的復(fù)用機會,因為PO只包含getXXX同setXXX類似的屬性。
          然而這正類似面向過程模式的范例,使用方法操作結(jié)構(gòu),程序多少又回到了面向過程的方式。
          2.將PO直接傳遞到應(yīng)用層,迫使應(yīng)用層依賴于業(yè)務(wù)層,如果一個人同時負責(zé)應(yīng)用層和業(yè)務(wù)層那么問題不大;
          如果是分別由不同的人開發(fā),將使得應(yīng)用層開發(fā)人員必須了解業(yè)務(wù)層對象結(jié)構(gòu)的細節(jié),增加了應(yīng)用層開發(fā)人員的知識范圍。
          同時因為這種耦合,開發(fā)的并行受到影響,相互交流增多。
          3.此外這也會使得業(yè)務(wù)層在構(gòu)建PO時要特別小心,因為需要考慮傳遞到應(yīng)用層效率問題,在構(gòu)建業(yè)務(wù)層時需要
          考慮應(yīng)用層的需要解決的問題是不是有些奇怪?

          有人會抱怨寫XXXAssember太麻煩,我的經(jīng)驗是XXXAssembler都很簡單。

          我們使用手機,會發(fā)現(xiàn)大多數(shù)手機提供給的接口都是相同的,這包括0-9的數(shù)字鍵,綠色的接聽鍵,紅色的掛機鍵,還有一塊顯示屏。
          無論我是拿到NOkIA,還是MOTO的手機,我都能使用,作為手機使用者我沒有必要知道手機界面下的結(jié)構(gòu),不用關(guān)心
          使用的是SmartPhone還是Symbian。

          確實,應(yīng)用層將服務(wù)層和業(yè)務(wù)層看作黑箱要比看作白箱好得多。

          主站蜘蛛池模板: 崇仁县| 墨玉县| 东乌珠穆沁旗| 涞源县| 屏东市| 青神县| 扶余县| 常山县| 河池市| 隆昌县| 乌苏市| 甘孜| 枣庄市| 上犹县| 日照市| 宜城市| 松滋市| 潍坊市| 大渡口区| 芷江| 松潘县| 吉隆县| 万安县| 乳源| 华坪县| 余干县| 三河市| 琼中| 夏津县| 噶尔县| 霍邱县| 大厂| 嘉禾县| 伊宁市| 西乌珠穆沁旗| 松江区| 东宁县| 商河县| 九江县| 湖南省| 株洲县|