java 分层领域模型_Java领域模型 | 学步园

为了补大家的遗憾,在此总结下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层过多的信息,可能引起意想不到的副作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值