有兴趣可看此处原文及相关讨论: 总结一下最近关于domain object以及相关的讨论
======================================
在最近的围绕domain object的讨论中浮现出来了三种模型,(还有一些其他的旁枝,不一一分析了),经过一番讨论,各种问题逐渐清晰起来,在这里我试图做一个总结,便于大家了解和掌握。
第一种模型:只有getter/setter方法的纯数据类,所有的业务逻辑完全由business object来完成(又称TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”。下面用举一个具体的代码来说明,代码来自Hibernate的caveatemptor,但经过我的改写:
一个实体类叫做Item,指的是一个拍卖项目
一个DAO接口类叫做ItemDao
一个DAO接口实现类叫做ItemDaoHibernateImpl
一个业务逻辑类叫做ItemManager(或者叫做ItemService)
- 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方法省略不写,避免篇幅太长
- }
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方法省略不写,避免篇幅太长 }
- public interface ItemDao {
- public Item getItemById(Long id);
- public Collection findAll();
- public void updateItem(Item item);
- }
public interface ItemDao { public Item getItemById(Long id); public Collection findAll(); public void updateItem(Item item); }
ItemDao定义持久化操作的接口,用于隔离持久化代码。
- 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);
- }
- }
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中,保证 一次调用只打开关闭一次)
- 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;
- }
- }
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方法。
第二种模型,也就是Martin Fowler指的rich domain object是下面这样子的:
一个带有业务逻辑的实体类,即domain object是Item
一个DAO接口ItemDao
一个DAO实现ItemDaoHibernateImpl
一个业务逻辑对象ItemManager
- 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;
- }
- }
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的代码同上,省略。
- 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,保持持久化
- }
- }
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中。
我提出的原则是:看业务方法是否显式的依赖持久化。
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和上面完全相同,就省略了。
- 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;
- }
- }
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中实现。
转自:http://www.iteye.com/topic/11712