1. 程式人生 > >(轉)總結一下最近關於domain object以及相關的討論(來自JavaEye,作者robin)

(轉)總結一下最近關於domain object以及相關的討論(來自JavaEye,作者robin)

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

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

一個實體類叫做Item,指的是一個拍賣專案 
一個DAO介面類叫做ItemDao 
一個DAO介面實現類叫做ItemDaoHibernateImpl 
一個業務邏輯類叫做ItemManager(或者叫做ItemService)

publicclass Item implements Serializable 
    
private Long id =null
    
privateint 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方法省略不寫,避免篇幅太長 
}
publicinterface
 ItemDao 
    
public Item getItemById(Long id); 
    
public Collection findAll(); 
    
publicvoid updateItem(Item item); 
}

ItemDao定義持久化操作的介面,用於隔離持久化程式碼。 

publicclass 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"); 
    }
 
    
publicvoid updateItem(Item item) 
        getHibernateTemplate().update(item); 
    }
 
}


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

publicclass ItemManager 
    
private ItemDao itemDao; 
    
publicvoid setItemDao(ItemDao itemDao) this.itemDao = itemDao;} 
    
public Bid loadItemById(Long id) 
        itemDao.loadItemById(id); 
    }
 
    
public Collection listAllItems() 
        
return  itemDao.findAll(); 
    }
 
    
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException 

            
if (currentMaxBid !=null&& currentMaxBid.getAmount().compareTo(bidAmount) >0
            
thrownew BusinessException("Bid too low."); 
    }
 
    
    
// Auction is active 
if ( !state.equals(ItemState.ACTIVE) ) 
            
thrownew BusinessException("Auction is not active yet."); 
    
    
// Auction still valid 
if ( item.getEndDate().before( new Date() ) ) 
            
thrownew BusinessException("Can't place new bid, auction already ended."); 
    
    
// Create new Bid 
    Bid newBid =new Bid(bidAmount, item, bidder); 
    
    
// Place bid for this Item 
    item.getBids().add(newBid); 
    itemDao.update(item);     
//  呼叫DAO完成持久化操作 
return newBid; 
    }
 
}

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

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

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

一個帶有業務邏輯的實體類,即domain object是Item 
一個DAO介面ItemDao 
一個DAO實現ItemDaoHibernateImpl 
一個業務邏輯物件ItemManager 

publicclass Item implements Serializable 
    
//  所有的屬性和getter/setter方法同上,省略 
public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                        Bid currentMaxBid, Bid currentMinBid) 
            throws BusinessException 

    
            
// Check highest bid (can also be a different Strategy (pattern)) 
if (currentMaxBid !=null&& currentMaxBid.getAmount().compareTo(bidAmount) >0
                    
thrownew BusinessException("Bid too low."); 
            }
 
    
            
// Auction is active 
if ( !state.equals(ItemState.ACTIVE) ) 
                    
thrownew BusinessException("Auction is not active yet."); 
    
            
// Auction still valid 
if ( this.getEndDate().before( new Date() ) ) 
                    
thrownew BusinessException("Can't place new bid, auction already ended."); 
    
            
// Create new Bid 
            Bid newBid =new Bid(bidAmount, this, bidder); 
    
            
// Place bid for this Item 
this.getBids.add(newBid);  // 請注意這一句,透明的進行了持久化,但是不能在這裡呼叫ItemDao,Item不能對ItemDao產生依賴! 
    
            
return newBid; 
    }
 
}


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

ItemDao和ItemDaoHibernateImpl的程式碼同上,省略。 

publicclass ItemManager 
    
private ItemDao itemDao; 
    
publicvoid setItemDao(ItemDao itemDao) this.itemDao = itemDao;} 
    
public Bid loadItemById(Long id) 
        itemDao.loadItemById(id); 
    }
 
    
public Collection listAllItems() 
        
return  itemDao.findAll(); 
    }
 
    
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException 

        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和上面完全相同,就省略了。 

publicclass Item implements Serializable 
    
//  所有的屬性和getter/setter方法都省略 
privatestatic ItemDao itemDao; 
    
publicvoid setItemDao(ItemDao itemDao) {this.itemDao = itemDao;} 
    
    
publicstatic Item loadItemById(Long id) 
        
return (Item) itemDao.loadItemById(id); 
    }
 
    
publicstatic Collection findAll() 
        
return (List) itemDao.findAll(); 
    }
 

    
public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                    Bid currentMaxBid, Bid currentMinBid) 
    throws BusinessException 

    
        
// Check highest bid (can also be a different Strategy (pattern)) 
if (currentMaxBid !=null&& currentMaxBid.getAmount().compareTo(bidAmount) >0
                
thrownew BusinessException("Bid too low."); 
        }
 
        
        
// Auction is active 
if ( !state.equals(ItemState.ACTIVE) ) 
                
thrownew BusinessException("Auction is not active yet."); 
        
        
// Auction still valid 
if ( this.getEndDate().before( new Date() ) ) 
                
thrownew BusinessException("Can't place new bid, auction already ended."); 
        
        
// Create new Bid 
        Bid newBid =new Bid(bidAmount, this, bidder); 
        
        
// Place bid for this Item 
this.addBid(newBid); 
        itemDao.update(
this);      //  呼叫DAO進行顯式持久化 
return newBid; 
    }
 
}

在上面三種模型之外,還有很多這三種模型的變種,例如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流行以前,我們只能用第一種模型,第二種模型那時候是不現實的)。

既然大家都統一了觀點,那麼就有了一個很好的討論問題的基礎了。Martin Fowler的Domain Model,或者說我們的第二種模型難道是完美無缺的嗎?當然不是,接下來我就要分析一下它的不足,以及可能的解決辦法,而這些都來源於我個人的實踐探索。 

在第二種模型中,我們可以清楚的把這4個類分為三層: 

1、實體類層,即Item,帶有domain logic的domain object 
2、DAO層,即ItemDao和ItemDaoHibernateImpl,抽象持久化操作的介面和實現類 
3、業務邏輯層,即ItemManager,接受容器事務控制,向Web層提供統一的服務呼叫 

在這三層中我們大家可以看到,domain object和DAO都是非常穩定的層,其實原因也很簡單,因為domain object是對映資料庫欄位的,資料庫欄位不會頻繁變動,所以domain object也相對穩定,而面向資料庫持久化程式設計的DAO層也不過就是CRUD而已,不會有更多的花樣,所以也很穩定。 

問題就在於這個充當business workflow facade的業務邏輯物件,它的變動是相當頻繁的。業務邏輯物件通常都是無狀態的、受事務控制的、Singleton類,我們可以考察一下業務邏輯物件都有哪幾類業務邏輯方法: 

第一類:DAO介面方法的代理,就是上面例子中的loadItemById方法和findAll方法。 

ItemManager之所以要代理這種類,目的有兩個:向Web層提供統一的服務呼叫入口點和給持久化方法增加事務控制功能。這兩點都很容易理解,你不能既給Web層程式設計師提供xxxManager,也給他提供xxxDao,所以你需要用xxxManager封裝xxxDao,在這裡,充當了一個簡單代理功能;而事務控制也是持久化方法必須的,事務可能需要跨越多個DAO方法呼叫,所以必須放在業務邏輯層,而不能放在DAO層。 

但是必須看到,對於一個典型的web應用來說,絕大多數的業務邏輯都是簡單的CRUD邏輯,所以這種情況下,針對每個DAO方法,xxxManager都需要提供一個對應的封裝方法,這不但是非常枯燥的,也是令人感覺非常不好的。 


第二類:domain logic的方法代理。就是上面例子中placeBid方法。雖然Item已經有了placeBid方法,但是ItemManager仍然需要封裝一下Item的placeBid,然後再提供一個簡單封裝之後的代理方法。 

這和第一種情況類似,其原因也一樣,也是為了給Web層提供一個統一的服務呼叫入口點和給隱式的持久化動作提供事務控制。 

同樣,和第一種情況一樣,針對每個domain logic方法,xxxManager都需要提供一個對應的封裝方法,同樣是枯燥的,令人不爽的。 


第三類:需要多個domain object和DAO參與協作的business workflow。這種情況是業務邏輯物件真正應該完成的職責。 

在這個簡單的例子中,沒有涉及到這種情況,不過大家都可以想像的出來這種應用場景,因此不必舉例說明了。 

通過上面的分析可以看出,只有第三類業務邏輯方法才是業務邏輯物件真正應該承擔的職責,而前兩類業務邏輯方法都是“無奈之舉”,不得不為之的事情,不但枯燥,而且令人沮喪。 

分析完了業務邏輯物件,我們再回頭看一下domain object,我們要仔細考察一下domain logic的話,會發現domain logic也分為兩類: 

第一類:需要持久層框架隱式的實現透明持久化的domain logic,例如Item的placeBid方法中的這一句:

this.getBids().add(newBid);

上面已經著重提到,雖然這僅僅只是一個Java集合的新增新元素的操作,但是實際上通過事務的控制,會潛在的觸發兩條SQL:一條是insert一條記錄到bid表,一條是更新item表相應的記錄。如果我們讓Item脫離Hibernate進行單元測試,它就是一個單純的Java集合操作,如果我們把他加入到Hibernate框架中,他就會潛在的觸發兩條SQL,這就是隱式的依賴於持久化的domain logic。 
特別請注意的一點是:在沒有Hibernate/JDO這類可以實現“透明的持久化”工具出現之前,這類domain logic是無法實現的。 

對於這一類domain logic,業務邏輯物件必須提供相應的封裝方法,以實現事務控制。 


第二類:完全不依賴持久化的domain logic,例如readonly例子中的Topic,如下: 

class Topic 
    boolean isAllowReply() 

   
 
 </div> 
 <div class=

相關推薦

總結一下最近關於domain object以及相關的討論來自JavaEye作者robin

在最近的圍繞domain object的討論中浮現出來了三種模型,(還有一些其他的旁枝,不一一分析了),經過一番討論,各種問題逐漸清晰起來,在這裡我試圖做一個總結,便於大家瞭解和掌握。 第一種模型:只有getter/setter方法的純資料類,所有的業務邏輯完全由business object來完成(又稱T

今天總結一下我對Fragment的理解碎片的入棧與出棧碎片的巢狀

1.碎片的巢狀! getFragmentManager到的是activity對所包含fragment的Manager,而如果是fragment巢狀fragment,那麼就需要利用getChildFragmentManager()了。 getFragmentManager(

常見電源品牌大揭密貼自pceva作者royalk

功能 測試 出貨量 可靠的 忽略 產品分類 穩定 娛樂 軟件 常見電源品牌大揭密(轉貼自pceva,作者royalk) 介紹電源品牌代工廠之前,必須介紹一下電源分類: 標準電源 標準電源就是電腦城裝機用得最多的電源,性能正常,外觀一般,原生接線,也沒有什麽

APICloud框架——總結一下最近開發APP遇到的一些問題 (二)

高度自適應 flex佈局 允許子元素伸縮 手機號正則 function checkPhone(data){ if(!(/^1[34578]\d{9}$/.test(data))){ alert("手機號碼有誤,請重填")

APICloud框架——總結一下最近開發APP遇到的一些問題 (三)

ajax報錯 Uncaught DOMException: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 需要在伺服器環境下執行, 不能直接雙擊開啟 # 七牛雲 新建圖片樣式可

勞動節前得空半天-總結一下最近使用的LINUX命令

ava less 查找內容 一行 文件中 顯示行號 搜索 高亮 linux命令 一、搜索文件   1、locate xxx.log 全盤搜索xxx.log文件   2、which java 查找命令   3、ll xxx.log 在目

ubntu下單機配置fastdfs作為開發環境3--- nginx如何啟用fastdfs擴充套件以及相關配置

前言 參考: FastDFS+Nginx(單點部署)事例 FastDFS搭建單機圖片伺服器(二) 配置過程簡介 1.配置mod-fastdfs.conf,並拷貝到/etc/fdfs檔案目錄下。 建立nginx存放日誌和資料的目錄 mkdir /home/fastdfs

Tableau已經安裝且重灌時提示試用期結束了怎麼辦?該方法已經過期!請到官網下載免費版謝謝!

 本文僅做研究使用,支援大家購買正版。 A類使用者:如果已經安裝Tableau,並且沒有過期,見(一)! B類使用者:如果已經安裝Tableau,並且過期了,現已找到方法幫大家tableau家族解決這個問題了。見(二)。 (以下提供了方案1解決A類使用者問題,方案2解

Java 垃圾回收機制以及怎麼減少呼叫GC提高效能

 綜合了若干人的blog~ 1. 垃圾回收的意義  在C++中,物件所佔的記憶體在程式結束執行之前一直被佔用,在明確釋放之前不能分配給其它物件;而在Java中,當沒有物件引用指向原先分配給某個物件的記憶體時,該記憶體便成為垃圾。JVM的一個系統級執行緒會自動釋放該記憶體

最詳細的 Android Toolbar 開發實踐總結

activity resource listener nba flat xmlns mat https ons 轉自:http://www.codeceo.com/article/android-toolbar-develop.html 過年前發了一篇介紹 Transluc

C++中的static關鍵字的總結

blank protected .com 如果 發現 內部實現 屬於 out 服務  C++的static有兩種用法:面向過程程序設計中的static和面向對象程序設計中的static。前者應用於普通變量和函數,不涉及類;後者主要說明static在類中的作用。 1.面向過程

基於MVC4+EasyUI的Web開發框架經驗總結5--使用HTML編輯控件CKEditor和CKFinder

err config 兩個 腳本 web開發 upload asp 正常 初始 http://www.cnblogs.com/wuhuacong/p/3780356.html Web開發上有很多HTML的編輯控件,如CKEditor、kindeditor等等,很多都做的很

基於MVC4+EasyUI的Web開發框架經驗總結2- 使用EasyUI的樹控件構建Web界面

set 應用 get ember trim ase str zab ble http://www.cnblogs.com/wuhuacong/p/3669575.html 最近花了不少時間在重構和進一步提煉我的Web開發框架上,力求在用戶體驗和界面設計方面,和Winfor

基於MVC4+EasyUI的Web開發框架經驗總結6--在頁面中應用下拉列表的處理

ica new web開發 don ext images 如果 bob 獲取 http://www.cnblogs.com/wuhuacong/p/3840321.html 在很多Web界面中,我們都可以看到很多下拉列表的元素,有些是固定的,有些是動態的;有些是字典內容,

基於MVC4+EasyUI的Web開發框架經驗總結8--實現Office文檔的預覽

討論 off info code .cn viewer 存在 nco app http://www.cnblogs.com/wuhuacong/p/3871991.html 基於MVC4+EasyUI的Web開發框架經驗總結(8)--實現Office文檔的預覽

Tarjan應用:求割點/橋/縮點/強連通分量/雙連通分量/LCA(最近公共祖先)

應用 說明 lca ref father 無向圖 沒有 經理 遠的 本文轉載自:http://hi.baidu.com/lydrainbowcat/item/f8a5ac223e092b52c28d591c 作者提示:在閱讀本文之前,請確保您已經理解並掌握了基本的T

erlang程序優化點的總結

數據庫 機器 ria 嚴重 多線程 分別是 簡單 構造 代碼實現 註意,這裏只是給出一個總結,具體性能需要根據實際環境和需要來確定 霸爺指出,新的erlang虛擬機有很多調優啟動參數,今後現在這個方面深挖一下。 1. 進程標誌設置: 消息和binary內

Java IO流學習總結

rar output 出現 arr system 不存在 技術分享 輸出 寫入 原文地址:http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html Java流操作有關的類或接口: Java流類圖結構:

集成學習算法總結----Boosting和Bagging

原理 過程 訓練 嚴重 oos 機器學習 ppr 次數 error 1、集成學習概述 1.1 集成學習概述 集成學習在機器學習算法中具有較高的準去率,不足之處就是模型的訓練過程可能比較復雜,效率不是很高。目前接觸較多的集成學習主要有2種:基於Boosting的和基於Bagg

C#中WinForm程序退出方法技巧總結

ren sender body 登錄 ble 按鈕 動作 打開 alt 一、關閉窗體 在c#中退出WinForm程序包括有很多方法,如:this.Close(); Application.Exit();Application.ExitThread(); System.E