posts - 89,  comments - 98,  trackbacks - 0

          在最近的圍繞domain object的討論中浮現出來了三種模型,(還有一些其他的旁枝,不一一分析了),經過一番討論,各種問題逐漸清晰起來,在這里我試圖做一個總結,便于大家了解和掌握。

          第一種模型:只有getter/setter方法的純數據類,所有的業務邏輯完全由business object來完成(又稱TransactionScript),這種模型下的domain object被Martin Fowler稱之為“貧血的domain object”。下面用舉一個具體的代碼來說明,代碼來自Hibernate的caveatemptor,但經過我的改寫:

          一個實體類叫做Item,指的是一個拍賣項目
          一個DAO接口類叫做ItemDao
          一個DAO接口實現類叫做ItemDaoHibernateImpl
          一個業務邏輯類叫做ItemManager(或者叫做ItemService)

          java代碼
          						public
          						class 
          						Item
          						implements
          						Serializable
          						{
          						
          private Long id = null ;
          private int version ;
          private String name ;
          private User seller ;
          private String description ;
          private MonetaryAmount initialPrice ;
          private MonetaryAmount reservePrice ;
          private Date startDate ;
          private Date endDate ;
          private Set categorizedItems = new HashSet ();
          private Collection bids = new ArrayList ();
          private Bid successfulBid ;
          private ItemState state ;
          private User approvedBy ;
          private Date approvalDatetime ;
          private Date created = new Date ();
          / / getter / setter方法省略不寫,避免篇幅太長
          }

          java代碼
          						public
          						interface
          						ItemDao
          						{
          						
          public Item getItemById ( Long id );
          public Collection findAll ();
          public void updateItem ( Item item );
          }

          ItemDao定義持久化操作的接口,用于隔離持久化代碼。

          java代碼
          						public
          						class 
          						ItemDaoHibernateImpl
          						implements
          						ItemDao
          						extends
          						HibernateDaoSupport
          						{
          						
          public Item getItemById ( Long id ) {
          return ( Item ) getHibernateTemplate (). load ( Item . class , id );
          }
          public Collection findAll () {
          return ( List ) getHibernateTemplate (). find (" from Item ");
          }
          public void updateItem ( Item item ) {
          getHibernateTemplate (). update ( item );
          }
          }

          ItemDaoHibernateImpl完成具體的持久化工作,請注意,數據庫資源的獲取和釋放是在ItemDaoHibernateImpl里面處理的,每個DAO方法調用之前打開Session,DAO方法調用之后,關閉Session。(Session放在ThreadLocal中,保證一次調用只打開關閉一次)

          java代碼
          publicclass ItemManager{
          privateItemDaoitemDao;
          publicvoidsetItemDao(ItemDaoitemDao){this.itemDao=itemDao;}
          publicBidloadItemById(Longid){
          itemDao.loadItemById(id);
          }
          publicCollectionlistAllItems(){
          returnitemDao.findAll();
          }
          publicBidplaceBid(Itemitem,Userbidder,MonetaryAmountbidAmount,
          BidcurrentMaxBid,BidcurrentMinBid)throwsBusinessException{
          if(currentMaxBid!=null&&currentMaxBid.getAmount().compareTo(bidAmount)>0){
          thrownewBusinessException("Bid too low.");
          }

          //Auctionisactive
          if(!state.equals(ItemState.ACTIVE))
          thrownewBusinessException("Auction is not active yet.");

          //Auctionstillvalid
          if(item.getEndDate().before(newDate()))
          thrownewBusinessException("Can't place new bid, auction already ended.");

          //CreatenewBid
          BidnewBid=newBid(bidAmount,item,bidder);

          //PlacebidforthisItem
          item.getBids().add(newBid);
          itemDao.update(item);// 調用DAO完成持久化操作
          returnnewBid;
          }
          }

          事務的管理是在ItemManger這一層完成的,ItemManager實現具體的業務邏輯。除了常見的和CRUD有關的簡單邏輯之外,這里還有一個placeBid的邏輯,即項目的競標。

          以上是一個完整的第一種模型的示例代碼。在這個示例中,placeBid,loadItemById,findAll等等業務邏輯統統放在ItemManager中實現,而Item只有getter/setter方法。





          第二種模型,也就是Martin Fowler指的rich domain object是下面這樣子的:

          一個帶有業務邏輯的實體類,即domain object是Item
          一個DAO接口ItemDao
          一個DAO實現ItemDaoHibernateImpl
          一個業務邏輯對象ItemManager

          java代碼
          publicclass ItemimplementsSerializable{
          // 所有的屬性和getter/setter方法同上,省略
          publicBidplaceBid(Userbidder,MonetaryAmountbidAmount,
          BidcurrentMaxBid,BidcurrentMinBid)
          throwsBusinessException{

          //Checkhighestbid(canalsobeadifferentStrategy(pattern))
          if(currentMaxBid!=null&&currentMaxBid.getAmount().compareTo(bidAmount)>0){
          thrownewBusinessException("Bid too low.");
          }

          //Auctionisactive
          if(!state.equals(ItemState.ACTIVE))
          thrownewBusinessException("Auction is not active yet.");

          //Auctionstillvalid
          if(this.getEndDate().before(newDate()))
          thrownewBusinessException("Can't place new bid, auction already ended.");

          //CreatenewBid
          BidnewBid=newBid(bidAmount,this,bidder);

          //PlacebidforthisItem
          this.getBids.add(newBid);// 請注意這一句,透明的進行了持久化,但是不能在這里調用ItemDao,Item不能對ItemDao產生依賴!

          returnnewBid;
          }
          }

          競標這個業務邏輯被放入到Item中來。請注意this.getBids.add(newBid); 如果沒有Hibernate或者JDO這種O/R Mapping的支持,我們是無法實現這種透明的持久化行為的。但是請注意,Item里面不能去調用ItemDAO,對ItemDAO產生依賴!

          ItemDao和ItemDaoHibernateImpl的代碼同上,省略。

          java代碼
          publicclass ItemManager{
          privateItemDaoitemDao;
          publicvoidsetItemDao(ItemDaoitemDao){this.itemDao=itemDao;}
          publicBidloadItemById(Longid){
          itemDao.loadItemById(id);
          }
          publicCollectionlistAllItems(){
          returnitemDao.findAll();
          }
          publicBidplaceBid(Itemitem,Userbidder,MonetaryAmountbidAmount,
          BidcurrentMaxBid,BidcurrentMinBid)throwsBusinessException{
          item.placeBid(bidder,bidAmount,currentMaxBid,currentMinBid);
          itemDao.update(item);// 必須顯式的調用DAO,保持持久化
          }
          }

          在第二種模型中,placeBid業務邏輯是放在Item中實現的,而loadItemById和findAll業務邏輯是放在ItemManager中實現的。不過值得注意的是,即使placeBid業務邏輯放在Item中,你仍然需要在ItemManager中簡單的封裝一層,以保證對placeBid業務邏輯進行事務的管理和持久化的觸發。

          這種模型是Martin Fowler所指的真正的domain model。在這種模型中,有三個業務邏輯方法:placeBid,loadItemById和findAll,現在的問題是哪個邏輯應該放在Item中,哪個邏輯應該放在ItemManager中。在我們這個例子中,placeBid放在Item中(但是ItemManager也需要對它進行簡單的封裝),loadItemById和findAll是放在ItemManager中的。

          切分的原則是什么呢? Rod Johnson提出原則是“case by case”,可重用度高的,和domain object狀態密切關聯的放在Item中,可重用度低的,和domain object狀態沒有密切關聯的放在ItemManager中。

          我提出的原則是:看業務方法是否顯式的依賴持久化。

          Item的placeBid這個業務邏輯方法沒有顯式的對持久化ItemDao接口產生依賴,所以要放在Item中。請注意,如果脫離了Hibernate這個持久化框架,Item這個domain object是可以進行單元測試的,他不依賴于Hibernate的持久化機制。它是一個獨立的,可移植的,完整的,自包含的域對象

          而loadItemById和findAll這兩個業務邏輯方法是必須顯式的對持久化ItemDao接口產生依賴,否則這個業務邏輯就無法完成。如果你要把這兩個方法放在Item中,那么Item就無法脫離Hibernate框架,無法在Hibernate框架之外獨立存在。



          第三種模型印象中好像是firebody或者是Archie提出的(也有可能不是,記不清楚了),簡單的來說,這種模型就是把第二種模型的domain object和business object合二為一了。所以ItemManager就不需要了,在這種模型下面,只有三個類,他們分別是:

          Item:包含了實體類信息,也包含了所有的業務邏輯
          ItemDao:持久化DAO接口類
          ItemDaoHibernateImpl:DAO接口的實現類

          由于ItemDao和ItemDaoHibernateImpl和上面完全相同,就省略了。

          java代碼
          publicclass ItemimplementsSerializable{
          // 所有的屬性和getter/setter方法都省略
          privatestaticItemDaoitemDao;
          publicvoidsetItemDao(ItemDaoitemDao){this.itemDao=itemDao;}

          publicstaticItemloadItemById(Longid){
          return(Item)itemDao.loadItemById(id);
          }
          publicstaticCollectionfindAll(){
          return(List)itemDao.findAll();
          }

          publicBidplaceBid(Userbidder,MonetaryAmountbidAmount,
          BidcurrentMaxBid,BidcurrentMinBid)
          throwsBusinessException{

          //Checkhighestbid(canalsobeadifferentStrategy(pattern))
          if(currentMaxBid!=null&&currentMaxBid.getAmount().compareTo(bidAmount)>0){
          thrownewBusinessException("Bid too low.");
          }

          //Auctionisactive
          if(!state.equals(ItemState.ACTIVE))
          thrownewBusinessException("Auction is not active yet.");

          //Auctionstillvalid
          if(this.getEndDate().before(newDate()))
          thrownewBusinessException("Can't place new bid, auction already ended.");

          //CreatenewBid
          BidnewBid=newBid(bidAmount,this,bidder);

          //PlacebidforthisItem
          this.addBid(newBid);
          itemDao.update(this);// 調用DAO進行顯式持久化
          returnnewBid;
          }
          }

          在這種模型中,所有的業務邏輯全部都在Item中,事務管理也在Item中實現。

          posted on 2006-10-19 09:19 水煮三國 閱讀(516) 評論(2)  編輯  收藏 所屬分類: J2EEHibernate

          FeedBack:
          # re: 總結一下最近關于domain object以及相關的討論 [From:JAVAEYE]
          2006-10-19 09:20 | 水煮三國
          在上面三種模型之外,還有很多這三種模型的變種,例如partech的模型就是把第二種模型中的DAO和Manager三個類合并為一個類后形成的模型;例如frain....(id很長記不住)的模型就是把第三種模型的三個類完全合并為一個單類后形成的模型;例如Archie是把第三種模型的Item又分出來一些純數據類(可能是,不確定)形成的一個模型。

          但是不管怎么變,基本模型歸納起來就是上面的三種模型,下面分別簡單評價一下:

          第一種模型絕大多數人都反對,因此反對理由我也不多講了。但遺憾的是,我觀察到的實際情形是,很多使用Hibernate的公司最后都是這種模型,這里面有很大的原因是很多公司的技術水平沒有達到這種層次,所以導致了這種貧血模型的出現。從這一點來說,Martin Fowler的批評聲音不是太響了,而是太弱了,還需要再繼續吶喊。

          第二種模型就是Martin Fowler一直主張的模型,實際上也是我一直在實際項目中采用這種模型。我沒有看過Martin的POEAA,之所以能夠自己摸索到這種模型,也是因為從02年我已經開始思考這個問題并且尋求解決方案了,但是當時沒有看到Hibernate,那時候做的一個小型項目我已經按照這種模型來做了,但是由于沒有O/R Mapping的支持,寫到后來又不得不全部改成貧血的domain object,項目做完以后再繼續找,隨后就發現了Hibernate。當然,現在很多人一開始就是用Hibernate做項目,沒有經歷過我經歷的那個階段。

          不過我覺得這種模型仍然不夠完美,因為你還是需要一個業務邏輯層來封裝所有的domain logic,這顯得非常羅嗦,并且業務邏輯對象的接口也不夠穩定。如果不考慮業務邏輯對象的重用性的話(業務邏輯對象的可重用性也不可能好),很多人干脆就去掉了xxxManager這一層,在Web層的Action代碼直接調用xxxDao,同時容器事務管理配置到Action這一層上來。Hibernate的caveatemptor就是這樣架構的一個典型應用。

          第三種模型是我很反對的一種模型,這種模型下面,Domain Object和DAO形成了雙向依賴關系,無法脫離框架測試,并且業務邏輯層的服務也和持久層對象的狀態耦合到了一起,會造成程序的高度的復雜性,很差的靈活性和糟糕的可維護性。也許將來技術進步導致的O/R Mapping管理下的domain object發展到足夠的動態持久透明化的話,這種模型才會成為一個理想的選擇。就像O/R Mapping的流行使得第二種模型成為了可能(O/R Mapping流行以前,我們只能用第一種模型,第二種模型那時候是不現實的)。
            回復  更多評論
            
          # re: 總結一下最近關于domain object以及相關的討論 [From:JAVAEYE]
          2006-10-19 09:21 | 水煮三國
          總結的不錯!我也來湊個熱鬧吧,發發自己的觀點。

          Robbin總結的三點模型,我1、2用過,所味的第3點還無福消受啊

          一、關于第一種模型我在一個實際項目中做過這樣的設計,給我的感覺不是很好,主要有以下幾點:
          《1》要寫太多的貧血類,其實可以做一個工具來自動生成,也不錯。
          《2》業務邏輯感覺不能很好的得到體現,這樣增加了BussImp類的復雜度, 且許多操作是不應該由BussImp來承擔, 但也有個好處那就是設計相對的容易些
          《3》這不是OO的正統設計,這是受Boost的影響
          二、至于第二種模型是我一直都在用的,這種模型對設計人員的要求相對的要高,為什么?那是因為對 ItemClass 要比以前更難以把握,其實設計就是歸類,但就是這個歸類就不那么的簡單,且每個人都有自己的歸類方法,很難形成統一。
            回復  更多評論
            
          <2006年10月>
          24252627282930
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          常用鏈接

          留言簿(4)

          隨筆分類(85)

          隨筆檔案(89)

          文章分類(14)

          文章檔案(42)

          收藏夾(37)

          java

          oracle

          Sybase

          搜索

          •  

          積分與排名

          • 積分 - 211070
          • 排名 - 265

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 芮城县| 丹巴县| 岫岩| 山阴县| 清水河县| 洪湖市| 丽江市| 清原| 札达县| 东城区| 乌苏市| 固原市| 栖霞市| 宜都市| 成安县| 仪陇县| 连江县| 富川| 卢氏县| 秀山| 舞阳县| 八宿县| 中阳县| 沅江市| 康定县| 斗六市| 铁岭市| 蕲春县| 六盘水市| 淳安县| 郓城县| 封丘县| 荣成市| 绥阳县| 当雄县| 潮安县| 湘阴县| 湖南省| 奉贤区| 前郭尔| 宜宾县|