老妖的博客
          現實的中沒有幾個人能夠真為對方去死,甚至山盟海誓很快就會在金錢面前變的微不足道,這才是生活。沒有永遠的愛,除了你的父母對你,當然也就沒有永遠的恨,更沒有永遠的痛,時間是最好的治療大師,它會很快撫平你心靈上累累的傷痕。很多年以后你想起來時,那些在你生命中洶涌來往的人群至多是個模糊的影子或者毫無意義的名字
          posts - 105,  comments - 171,  trackbacks - 0
          Act上的擴展點

          按照分層的觀點,下層不允許依賴上層,然而業務對象卻是協作完成某個目的的。
          而且只要業務對象需要維護,就需要相關的Act。
          例如:銀行中的存錢業務,參考上面的分層,我們把它放入Operation層。
          在存錢的業務中,我們需要檢查該客戶是否做了掛失。而掛失協議我們是放在Commitment層。
          顯然,Operation層不能直接調用Commitment層的協議。
          DIP模式發話了“用我”。
          在Operation層中定義Commitment層接口,和一個工廠,使用反射實現這種調用。在Act中調用。
           1abstract public class ActImpl 
           2extends abstractActImpl 
           3implements Act 
           4
           5public virtual void run() 
           6
           7doPreprocess(); 
           8doRun(); 
           9doPostprocess(); 
          10}
           
          11abstract public doPreprocess(); 
          12abstract public doRun(); 
          13abstract public doPostprocess(); 
          14}
           
          15
          16public interface CustomerCommitment 
          17
          18void affirmCanDo(); 
          19}
           
          20
          21abstract public class CustomerActImpl 
          22extends ActImpl 
          23implements CustomerAct 
          24
          25 
          26public override void doPreprocess() 
          27
          28 
          29//擴展點 
          30CustomerCommitment customerCommitment = CustomerCommitmentFactory.create(this); 
          31customerCommitment.affirmCanDo(); 
          32 
          33}
           
          34 
          35}
           
          36
          37public interface InnerCustomerCommitment 
          38
          39void affirmCanDo(CustomerAct customerAct); 
          40}
           
          41
          42public class CustomerCommitmentImpl implements CustomerCommitment 
          43
          44private CustomerAct customerAct; 
          45
          46public CustomerCommitmentImpl(CustomerAct customerAct) 
          47
          48this.customerAct = customerAct; 
          49}
           
          50
          51public void affirmCanDo() 
          52
          53 
          54//通過配置得到該customerAct對應需要檢查的客戶約束,包括協議,逐一檢查。 
          55DomainObjectCollection commitmentTypes = CustomerCommimentRepository.findByBusinessType(customerAct.getBusinessType()); 
          56
          57 
          58foreach( CommitmentType typeItem in commitmentTypes ) 
          59
          60InnerCustomerCommitment commitment = getCommitment(typeItem); 
          61commitmentItem.affirmCanDo(customerAct); 
          62}
           
          63 
          64}
           
          65}
           
          66
          67public class CustomerLostReportAgreementChecker implements InnerCustomerCommitment 
          68
          69public void affirmCanDo(CustomerAct customerAct) 
          70
          71Check.require(customerAct.getCustomer() != null,"客戶不存在"); 
          72
          73CustomerLostReportAgreement customerLostReportAgreement = 
          74CustomerLostReportAgreementRepository.find(customerAct.getCustomer()); 
          75
          76if(customerLostReportAgreement != null
          77
          78agreement.affirmCanDo(customerAct); 
          79}
           
          80
          81}
           
          82}
           
          83
          84public class CustomerLostReportAgreement 
          85
          86 
          87public void AffirmCanDo(CustomerAct customerAct) 
          88
          89if(customerAct.getOccurDate <= expiringDate) 
          90throw new CustomerLossReportedException(customer); 
          91}
           
          92 
          93}
           
          94
          同樣道理,可以對其他上層的對象使用DIP使依賴倒置。
          比如:電信計算費用。就可以通過在CustomerAct的doRun中插入擴展點來實現。
          這樣復雜的計費算法就被封裝在接口之后了。可以分配另外的人員來開發。
          業務活動的流程仍然清晰可見。
          是啊,這正是接口的威力,大多數的設計模式不也是基于這種原理嗎?

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

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

          對于上面的情況,另外一種方法是使用Facade。
          讓我們比較一下兩者。
          簡要說明一下Facade的做法:
           1abstract public class CustomerActImpl 
           2extends ActImpl 
           3implements CustomerAct 
           4
           5 
           6public override void doPreprocess() 
           7
           8 
           9//注意:這里傳遞的參數,會使得用Facade方式的人大傷腦筋。 
          10//按照掛失的要求目前傳遞getBusinessType(),getCustomer(),getOccurDate()就夠了 
          11//但是對于所有的CustomerCommitment這些參數就不一定夠了。 
          12//比如:客戶可能簽訂指定員工協議。(指只允許協議中指明的員工能操作的業務) 
          13//那么該接口需要添加getOperator()參數。 
          14//接口變得不穩定。 
          15CustomerCommitmentManager.affirmCanDo(getBusinessType(),getCustomer(),getOccurDate(),?,); 
          16 
          17}
           
          18 
          19}
           
          20
          Facade可以使得在Act中也是只提供一個調用點,但是因為不是依賴倒置的關系,不得不顯示的說明需要用到的參數。
          相反使用DIP模式,接口中定義的是Act的接口,而Act是可以擴展的。(是否擴展全部看上層的對象是否需要)。
          而正是因為相應的CustomerCommitment總是處于需要檢查的XXXAct的上層。這樣具體的CustomerCommitment
          總是可以依賴XXXAct。因此可以獲得任何想要得到的信息。

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

          形象一點來說,使用DIP模式,采取的是一種專家模式。
          DIP的Act說的是:“CustomerCommitment你看看我現在的情況,還能運行嗎?”
          相反Facade模式,則是令人厭煩的嘮叨模式。
          Facade的Act說的是:“CustomerCommitment,現在執行的客戶是XXX,業務是XXX,時間是XXX,...你能告訴我還能運行下去嗎?”
          顯然DIP要瀟灑得多。
          實現接口 VS 繼承父類  

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

          “接口是調用方要求的結果,而繼承則是實現方思考的產物。”

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

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

          接口屬于調用方。
          業務對象的持久化

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

          但是這不代表業務層要依賴數據訪問層。相反依賴關系應該倒過來。數據訪問層依賴
          業務層。通常我們使用Mapper實現,在hibernate中通過配置達到該目的。
          要做到業務層不依賴于數據訪問層,同樣借助接口來完成。
          在業務層定義數據訪問的接口,為了方便,可以使用一個類來封裝這些操作。
           1public interface CustomerFinder 
           2
           3Customer findByID(ID id); 
           4Customer findByCode(String code); 
           5DomainObjectCollection findByName(String name); 
           6 
           7}
           
           8
           9public class CustomerRepository 
          10
          11private static CustomerFinder finder = null
          12private static CustomerFinder getFinderInstance() 
          13
          14if (finder == null
          15
          16finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder"); 
          17}
           
          18return finder; 
          19}
           
          20
          21public static Customer findByID(ID id) 
          22
          23Customer obj = getFinderInstance().findByID(id); 
          24Check.require(obj != null
          25"未找到ID為:" + id.toString() + 
          26"對應的 Customer。"); 
          27return obj; 
          28}
           
          29 
          30}
           
          31
          在數據訪問層實現這些接口。因為是數據訪問層依賴業務層,所以你可以采用多種技術來實現,
          使用hibernate這樣的開源項目,或者手工編寫Mapper。

          ID id

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

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

          現在我們向上看看將業務層包裹的服務層。
          服務層是架設在應用層和業務層的橋梁,用來封裝對業務層的訪問,因此
          可以把服務層看作中介,充當兩個角色:
          1.實現應用層接口要求的接口;
          2.作為業務層的外觀。
          服務層的典型調用如下:
            1public interface CustomerServices 
            2
            3void openCustomer(CustomerInfo cutomerInfo); 
            4void customerLostReport(String customerCode,Date expiringDate,String remark); 
            5CutomerBasicInfo getCutomerBasicInfo(String customerCode); 
            6 
            7}
           
            8
            9public class CustomerServicesImpl 
           10extends ServiceFacade 
           11implements CustomerServices 
           12
           13 
           14public void openCustomer(CustomerInfo cutomerInfo) 
           15
           16try 
           17
           18init(); 
           19
           20OpenCustomerAct openCustomerAct = 
           21new OpenCustomerAct(customerInfo.name, 
           22customerInfo.code, 
           23customerInfo.address, 
           24customerInfo.plainpassword 
           25 
           26); 
           27openCustomerAct.run(); 
           28
           29commit(); 
           30}
           
           31catch(Exception e) 
           32
           33throw ExceptionPostprocess(e); 
           34}
           
           35}
           
           36
           37public void customerLostReport(String customerCode,Date expiringDate,String remark) 
           38
           39try 
           40
           41Check.require(customerCode != null && customerCode != ""
           42"無效的客戶代碼:" + customerCode); 
           43init(); 
           44
           45CustomerLostReportAct customerLostReportAct = 
           46new CustomerLostReportAct(customerCode, 
           47expiringDate, 
           48remark); 
           49customerLostReportAct.run(); 
           50
           51commit(); 
           52}
           
           53catch(Exception e) 
           54
           55throw ExceptionPostprocess(e); 
           56}
           
           57}
           
           58
           59public CutomerBasicInfo getCutomerBasicInfo(String customerCode) 
           60
           61try 
           62
           63Check.require(customerCode != null && customerCode != ""
           64"無效的客戶代碼:" + customerCode); 
           65init(); 
           66Customer customer = CustomerRepository.findByCode(customerCode); 
           67
           68//這里選擇的是在CustomerRepository外拋出CustomerNotFoundException異常, 
           69//另一種方法是在CustomerRepository中拋出CustomerNotFoundException異常。 
           70//因為CustomerRepository在于通過客戶代碼查找對應的客戶。至于是否應該拋出 
           71//異常則交給業務層或服務層來處理。 
           72//這里有很微妙的區別,拋出CustomerNotFoundException應該是誰的職責呢? 
           73//你的想法是什么? 
           74if(customer == null
           75throw new CustomerNotFoundException(customerCode); 
           76
           77CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); 
           78return cutomerBasicInfo; 
           79}
           
           80catch(Exception e) 
           81
           82throw ExceptionPostprocess(e); 
           83}
           
           84}
           
           85
           86 
           87}
           
           88
           89服務層的代碼很簡單,不是嗎? 
           90
           91上面的代碼可以通過AOP進一步的簡化。使用AOP實現我希望代碼象下面這樣簡單。 
           92public class CustomerServicesImpl 
           93implements CustomerServices 
           94
           95 
           96public void openCustomer(CustomerInfo cutomerInfo) 
           97
           98OpenCustomerAct openCustomerAct = 
           99new OpenCustomerAct(customerInfo.name, 
          100customerInfo.code, 
          101customerInfo.address, 
          102customerInfo.plainpassword 
          103 
          104); 
          105openCustomerAct.run(); 
          106}
           
          107
          108public void customerLostReport(String customerCode,Date expiringDate,String remark) 
          109
          110Check.require(customerCode != null && customerCode != ""
          111"無效的客戶代碼:" + customerCode); 
          112CustomerLostReportAct customerLostReportAct = 
          113new CustomerLostReportAct(customerCode, 
          114expiringDate, 
          115remark); 
          116customerLostReportAct.run(); 
          117}
           
          118
          119public CutomerBasicInfo getCutomerBasicInfo(String customerCode) 
          120
          121Customer customer = CustomerRepository.findByCode(customerCode); 
          122if(customer == null
          123throw new CustomerNotFoundException(customerCode); 
          124
          125CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); 
          126return cutomerBasicInfo; 
          127}
           
          128
          DTO or Not

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

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

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

          確實,應用層將服務層和業務層看作黑箱要比看作白箱好得多。
          posted on 2005-09-29 00:41 老妖 閱讀(409) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          <2005年9月>
          28293031123
          45678910
          11121314151617
          18192021222324
          2526272829301
          2345678

          常用鏈接

          隨筆分類(48)

          隨筆檔案(104)

          好友鏈接

          我的豆瓣

          積分與排名

          • 積分 - 221198
          • 排名 - 257

          最新評論

          閱讀排行榜

          主站蜘蛛池模板: 蓝田县| 洮南市| 大渡口区| 皋兰县| 炎陵县| 静海县| 梅河口市| 新安县| 焦作市| 建德市| 巴林左旗| 河曲县| 陇川县| 闽清县| 云安县| 四子王旗| 南江县| 深州市| 安化县| 郁南县| 三原县| 南木林县| 绥中县| 灌云县| 高州市| 乐至县| 中山市| 扎囊县| 金川县| 江津市| 会东县| 丹棱县| 来凤县| 广南县| 罗甸县| 建瓯市| 文成县| 石家庄市| 永昌县| 鹰潭市| 且末县|