Java領域模型

來源:互聯網
上載者:User

為了補大家的遺憾,在此總結下ROBBIN的領域模型的一些觀點和大家的補充,在網站和演講中,robbin將領域模型初步分為4大類:
1,失血模型
2,貧血模型
3,充血模型
4,脹血模型
那麼讓我們看看究竟有這些領域模型的具體內容,以及他們的優缺點:

一、失血模型

失血模型簡單來說,就是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代碼: 

public class ItemManager {
    private ItemDao itemDao;
    public void 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) {
            throw new BusinessException("Bid too low.");
    }
   
    // Auction is active
    if ( !state.equals(ItemState.ACTIVE) )
            throw new BusinessException("Auction is not active yet.");
   
    // Auction still valid
    if ( item.getEndDate().before( new Date() ) )
            throw new 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方法。

二、貧血模型

簡單來說,就是domain ojbect包含了不依賴於持久化的領域邏輯,而那些依賴持久化的領域邏輯被分離到Service層。
Service(商務邏輯,事務封裝) --> DAO ---> domain object
這也就是Martin Fowler指的rich domain object

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

java代碼: 

public class 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) {
                    throw new BusinessException("Bid too low.");
            }
   
            // Auction is active
            if ( !state.equals(ItemState.ACTIVE) )
                    throw new BusinessException("Auction is not active yet.");
   
            // Auction still valid
            if ( this.getEndDate().before( new Date() ) )
                    throw new 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的代碼同上,省略。

java代碼: 

public class ItemManager {
    private ItemDao itemDao;
    public void 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中。

經過上面的討論,如何區分domain logic和business logic,我想提出一個改進的區分原則:

domain logic只應該和這一個domain object的執行個體狀態有關,而不應該和一批domain object的狀態有關;

當你把一個logic放到domain object中以後,這個domain object應該仍然獨立於持久層架構之外(Hibernate,JDO),這個domain object仍然可

以脫離持久層架構進行單元測試,這個domain object仍然是一個完備的,自包含的,不依賴於外部環境的領域對象,這種情況下,這個logic

才是domain logic。
這裡有一個很確定的原則:logic是否只和這個object的狀態有關,如果只和這個object有關,就是domain logic;如果logic是和一批domain

object的狀態有關,就不是domain logic,而是business logic。


Item的placeBid這個商務邏輯方法沒有顯式的對持久化ItemDao介面產生依賴,所以要放在Item中。請注意,如果脫離了Hibernate這個持久化

架構,Item這個domain object是可以進行單元測試的,他不依賴於Hibernate的持久化機制。它是一個獨立的,可移植的,完整的,自包含的

域對象。

而loadItemById和findAll這兩個商務邏輯方法是必須顯式的對持久化ItemDao介面產生依賴,否則這個商務邏輯就無法完成。如果你要把這兩

個方法放在Item中,那麼Item就無法脫離Hibernate架構,無法在Hibernate架構之外獨立存在。

這種模型的優點:
1、各層單向依賴,結構清楚,易於實現和維護
2、設計簡單易行,底層模型非常穩定
這種模型的缺點:
1、domain object的部分比較緊密依賴的持久化domain logic被分離到Service層,顯得不夠OO
2、Service層過於厚重

三、充血模型
充血模型和第二種模型差不多,所不同的就是如何劃分商務邏輯,即認為,絕大多商務邏輯都應該被放在domain object裡面(包括持久化邏輯)

,而Service層應該是很薄的一層,僅僅封裝事務和少量邏輯,不和DAO層打交道。
Service(事務封裝) ---> domain object <---> DAO
這種模型就是把第二種模型的domain object和business object合二為一了。所以ItemManager就不需要了,在這種模型下面,只有三個類,他

們分別是:

Item:包含了實體類資訊,也包含了所有的商務邏輯
ItemDao:持久化DAO介面類
ItemDaoHibernateImpl:DAO介面的實作類別

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

java代碼: 

public class Item implements Serializable {
    //  所有的屬性和getter/setter方法都省略
   private static ItemDao itemDao;
    public void setItemDao(ItemDao itemDao) {this.itemDao = itemDao;}
   
    public static Item loadItemById(Long id) {
        return (Item) itemDao.loadItemById(id);
    }
    public static 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) {
                throw new BusinessException("Bid too low.");
        }
       
        // Auction is active
        if ( !state.equals(ItemState.ACTIVE) )
                throw new BusinessException("Auction is not active yet.");
       
        // Auction still valid
        if ( this.getEndDate().before( new Date() ) )
                throw new 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;
    }
}



在這種模型中,所有的商務邏輯全部都在Item中,交易管理也在Item中實現。
這種模型的優點:
1、更加符合OO的原則
2、Service層很薄,只充當Facade的角色,不和DAO打交道。
這種模型的缺點:
1、DAO和domain object形成了雙向依賴,複雜的雙向依賴會導致很多潛在的問題。
2、如何劃分Service層邏輯和domain層邏輯是非常含混的,在實際項目中,由於設計和開發人員的水平差異,可能導致整個結構的混亂無序。
3、考慮到Service層的事務封裝特性,Service層必須對所有的domain object的邏輯提供相應的事務封裝方法,其結果就是Service完全重定義

一遍所有的domain logic,非常煩瑣,而且Service的事務化封裝其意義就等於把OO的domain logic轉換為過程的Service TransactionScript

。該充血模型辛辛苦苦在domain層實現的OO在Service層又變成了過程式,對於Web層程式員的角度來看,和貧血模型沒有什麼區別了。

1.事務我是不希望由Item管理的,而是由容器或更高一層的業務類來管理。

2.如果Item不脫離持久層的管理,如JDO的pm,那麼itemDao.update(this); 是不需要的,也就是說Item是在事務過程中從資料庫拿出來的,並

且聲明周期不超出當前事務的範圍。

3.如果Item是脫離持久層,也就是在Item的生命週期超出了事務的範圍,那就要必須顯示調用update或attach之類的持久化方法的,這種時候

就應該是按robbin所說的第2種模型來做。

四、脹血模型
基於充血模型的第三個缺點,有同學提出,乾脆取消Service層,只剩下domain object和DAO兩層,在domain object的domain logic上面封裝

事務。
domain object(事務封裝,商務邏輯) <---> DAO
似乎ruby on rails就是這種模型,他甚至把domain object和DAO都合并了。
該模型優點:
1、簡化了分層
2、也算符合OO
該模型缺點:
1、很多不是domain logic的service邏輯也被強行放入domain object ,引起了domain ojbect模型的不穩定
2、domain object暴露給web層過多的資訊,可能引起意想不到的副作用。  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.