(转载自)轻量级J2EE企业应用实战——Struts+Spring+Hibernate整合开发 李刚
常见的架构设计策略
目前流行的轻量级J2EE应用的架构比较一致,采用的技术也比较一致,通常使用Spring作为核心,向上整合MVC框架,向下整合ORM框架。使用Spring的IOC容器来管理各组件之间的依赖关系时,Spring的声明事务将负责业务逻辑层对象方法的事务管理。
但在固定的技术组合上,依然可能存在小的变化。下面依次讨论可能存在的架构策略。
8.4.1 贫血模式
贫血模式是最常用的设计架构,也是最容易理解的架构。为了让读者通过本书顺利进入轻量级J2EE企业应用开发,本书的第9章及第10章的范例都将采用这种简单的架构模式。
所谓贫血,指Domain Object只是单纯的数据类,不包含业务逻辑方法,即每个Domain Object类只包含基本的setter和getter方法。所有的业务逻辑都由业务逻辑组件实现,这种Domain Object就是所谓的贫血的Domain Object,采用这种Domain Object的架构即所谓的贫血模式。
下面以第9章的消息发布系统的部分代码为例,介绍贫血模式。
在贫血模式里,所有的Domain Object只是单纯的数据类,只包含每个属性的setter和getter方法,如下是两个持久化类。
第一个Domain Object是消息,其代码如下:
- public class News extends BaseObject implements Serializable
- {
- //主键
- private Long id;
- //消息标题
- private String title;
- //消息内容
- private String content;
- //消息的发布时间
- private Date postDate;
- //消息的最后修改时间
- private Date lastModifyDate;
- //消息所属分类
- private Category category;
- //消息对应的消息回复
- private Set newsReviews;
- //无参数的构造器
- public News() {
- }
- //消息回复对应的getter方法
- public Set getNewsReviews() {
- return newsReviews;
- }
- //消息回复对应的setter方法
- public void setNewsReviews(Set newsReviews) {
- this.newsReviews = newsReviews;
- }
- //消息分类对应的getter方法
- public Category getCategory() {
- return category;
- }
- //消息分类对应的setter方法
- public void setCategory(Category category) {
- this.category = category;
- }
- //消息最后修改时间的getter方法
- public Date getLastModifyDate() {
- return lastModifyDate;
- }
- //消息最后修改时间的setter方法
- public void setLastModifyDate(Date lastModifyDate) {
- this.lastModifyDate = lastModifyDate;
- }
- //消息发布时间的getter方法
- public Date getPostDate() {
- return postDate;
- }
- //消息发布时间的setter方法
- public void setPostDate(Date postDate) {
- this.postDate = postDate;
- }
- //消息内容对应的getter方法
- public String getContent() {
- return content;
- }
- //消息发布者对应的setter方法
- public void setContent(String content) {
- this.content = content;
- }
- //消息主键对应的getter方法
- public Long getId() {
- return id;
- }
- //消息主键对应的setter方法
- public void setId(Long id) {
- this.id = id;
- }
- //消息标题对应的getter方法
- public String getTitle() {
- return title;
- }
- //消息标题对应的setter方法
- public void setTitle(String title) {
- this.title = title;
- }
- //Domain Object重写equals方法
- public boolean equals(Object object) {
- if (!(object instanceof News)) {
- return false;
- }
- News rhs = (News) object;
- return this.poster.equals(rhs.getPoster())
- && this.postDate.equals(rhs.getPostDate());
- }
- //Domain Object重写的hashCode方法
- public int hashCode() {
- return this.poster.hashCode() + this.postDate.hashCode();
- }
- //Domain Object重写toString方法
- public String toString() {
- return new ToStringBuilder(this).append("id", this.id).append("title",
- this.title).append("postDate", this.postDate).append("content",
- this.content).append("lastModifyDate", this.lastModifyDate)
- .append("poster", this.poster)
- .append("category", this.category).append("newsReviews",
- this.newsReviews).toString();
- }
- }
第二个Domain Object是消息对应的回复,其代码如下:
- public class NewsReview extends BaseObject
- {
- //消息回复的主键
- private Long id;
- //消息回复的内容
- private String content;
- //消息回复的回复时间
- private Date postDate;
- //回复的最后修改时间
- private Date lastModifyDate;
- //回复的对应的消息
- private News news;
- //消息回复的构造器
- public NewsReview() {
- }
- //回复内容对应的getter方法
- public String getContent() {
- return content;
- }
- //回复内容对应的setter方法
- public void setContent(String content) {
- this.content = content;
- }
- //回复主键对应的setter方法
- public Long getId() {
- return id;
- }
- //回复主键对应的setter方法
- public void setId(Long id) {
- this.id = id;
- }
- //回复的最后修改时间对应的getter方法
- public Date getLastModifyDate() {
- return lastModifyDate;
- }
- //回复的最后修改时间对应的setter方法
- public void setLastModifyDate(Date lastModifyDate) {
- this.lastModifyDate = lastModifyDate;
- }
- //回复对应的消息的getter方法
- public News getNews() {
- return news;
- }
- //回复对应的消息的setter方法
- public void setNews(News news) {
- this.news = news;
- }
- //回复发布时间的getter方法
- public Date getPostDate() {
- return postDate;
- }
- //回复发布时间的setter方法
- public void setPostDate(Date postDate) {
- this.postDate = postDate;
- }
- //Domain Object重写的equals方法
- public boolean equals(Object object) {
- if (!(object instanceof NewsReview)) {
- return false;
- }
- NewsReview rhs = (NewsReview) object;
- return this.poster.equals(rhs.getPoster()) &&
- this.postDate.equals(rhs.getPostDate());
- /*return new EqualsBuilder().append(this.news, rhs.news).append(
- this.content, rhs.content).append(this.postDate, rhs.postDate)
- .append(this.lastModifyDate, rhs.lastModifyDate).append(
- this.id, rhs.id).append(this.poster, rhs.poster)
- .isEquals();
- */
- }
- //Domain Object对应的hashCode方法
- public int hashCode() {
- return this.poster.hashCode() + this.postDate.hashCode();
- /*return new HashCodeBuilder(-1152635115, 884310249).append(this.news)
- .append(this.content).append(this.postDate).append(
- this.lastModifyDate).append(this.id)
- .append(this.poster).toHashCode();
- */
- }
- //Domain Object对应的toString方法
- public String toString() {
- return new ToStringBuilder(this).append("id", this.id).append(
- "postDate", this.postDate).append("lastModifyDate",
- this.lastModifyDate).append("content", this.content).append(
- "poster", this.poster).append("news", this.news).toString();
- }
- }
从上面贫血模式的Domain Object可看出,其类代码中只有setter和getter方法,这种Domain Object只是单纯的数据体,类似于C的数据结构。虽然它的名字是Domain Object,却没有包含任何业务对象的相关方法。Martin Fowler认为,这是一种不健康的建模方式,Domain Model既然代表了业务对象,就应该包含相关的业务方法。从语言的角度上来说,Domain Model在这里被映射为Java对象(一般都是ORM), Java对象应该是数据与动作的集合,贫血模型相当于抛弃了Java面向对象的性质。
Rod Johnson和Martin Fowler一致认为:贫血的Domain Object实际上以数据结构代替了对象。他们认为Domain Object应该是个完整的Java 对象, 既包含基本的数据,也包含了操作数据相应的业务逻辑方法。
下面是NewsDAOHibernate的源代码,该DAO对象用于操作News对象:
- //NewsDAOHibernate继承HibernateDaoSupport,实现NewsDAO接口
- public class NewsDAOHibernate extends HibernateDaoSupport implements NewsDAO
- {
- //根据主键加载消息
- public News getNews(Long id)
- {
- News news = (News) getHibernateTemplate().get(News.class, id);
- if (news == null) {
- throw new ObjectRetrievalFailureException(News.class, id);
- }
- return news;
- }
- //保存新的消息
- public void saveNews(News news) {
- getHibernateTemplate().saveOrUpdate(news);
- }
- //根据主键删除消息
- public void removeNews(Long id)
- {
- getHibernateTemplate().delete(getNews(id));
- }
- //查找全部的消息
- public List findAll()
- {
- getHibernateTemplate().find("from News"));
- }
- }
既然DAO对象完成具体的持久化操作,因此基本的CRUD操作都应该在DAO对象中实现。但DAO对象应该包含多少个查询方法,并不是确定的。因此,根据业务逻辑的不同需要, 不同的DAO对象可能有数量不等的查询方法。
对于现实中News,应该包含一个业务方法(addNewsReviews方法)。在贫血模式下,News类的代码并没有包含该业务方法,只是将该业务方法放到业务逻辑对象中实现,下面是业务逻辑对象实现addNewsReviews的代码:
- public class FacadeManagerImpl implements FacadeManager
- {
- //业务逻辑对象依赖的DAO对象
- private CategoryDAO categoryDAO;
- private NewsDAO newsDAO;
- private NewsReviewDAO newsReviewDAO;
- private UserDAO userDAO;
- //...此处还应该增加依赖注入DAO对象必需的setter方法
- //...此处还应该增加其他业务逻辑方法
- //下面是增加新闻回复的业务方法
- public NewsReview addNewsReview(Long newsId , String content)
- {
- //根据新闻id加载新闻
- News news = newsDao.getNews(newsId);
- //以默认构造器创建新闻回复
- NewsReview review = new NewsReview();
- //设置新闻与新闻回复之间的关联
- review.setNews(news);
- //设置新闻回复的内容
- review.setContent(content);
- //设置回复的回复时间
- review.setPostDate(new Date());
- //设置新闻回复的最后修改时间
- review.setLastModifyDate(new Date());
- //保存回复
- newsReviewDAO.saveNewsReview(review);
- return review;
- }
- }
在贫血模式下,业务逻辑对象正面封装了全部的业务逻辑方法,Web层仅与业务逻辑组件交互即可,无须访问底层的DAO对象。Spring的声明式事务管理将负责业务逻辑对象方法的事务性。
在贫血模式下,其分层非常清晰。Domain Object 并不具备领域对象的业务逻辑功能,仅仅是ORM框架持久化所需的POJO,仅是数据载体。贫血模型容易理解,开发便捷,但严重背离了面向对象的设计思想,所有的Domain Object并不是完整的Java对象。
总结起来,贫血模式存在如下缺点:
— 项目需要书写大量的贫血类,当然也可以借助某些工具自动生成。
— Domain Object的业务逻辑得不到体现。由于业务逻辑对象的复杂度大大增加,许多不应该由业务逻辑对象实现的业务逻辑方法,完全由业务逻辑对象实现,从而使业务逻辑对象的实现类变得相当臃肿。
贫血模式的优点是:开发简单、分层清晰、架构明晰且不易混淆;所有的依赖都是单向依赖,解耦优秀。适合于初学者及对架构把握不十分清晰的开发团队。