`
justin8335
  • 浏览: 28249 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

常见SSH的架构设计策略(一)

阅读更多

(转载自)轻量级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是消息,其代码如下:

java 代码
  1. public class News extends BaseObject implements Serializable   
  2.   
  3. {   
  4.   
  5.     //主键   
  6.   
  7.     private Long id;   
  8.   
  9.     //消息标题   
  10.   
  11.     private String title;   
  12.   
  13.     //消息内容   
  14.   
  15.     private String content;   
  16.   
  17.     //消息的发布时间   
  18.   
  19.     private Date postDate;   
  20.   
  21.     //消息的最后修改时间   
  22.   
  23.     private Date lastModifyDate;   
  24.   
  25.     //消息所属分类   
  26.   
  27.     private Category category;   
  28.   
  29.     //消息对应的消息回复   
  30.   
  31.     private Set newsReviews;   
  32.   
  33.     //无参数的构造器   
  34.   
  35.     public News() {   
  36.   
  37.     }   
  38.   
  39.     //消息回复对应的getter方法   
  40.   
  41.     public Set getNewsReviews() {   
  42.   
  43.         return newsReviews;   
  44.   
  45.     }   
  46.   
  47.     //消息回复对应的setter方法   
  48.   
  49.     public void setNewsReviews(Set newsReviews) {   
  50.   
  51.         this.newsReviews = newsReviews;   
  52.   
  53.     }   
  54.   
  55.     //消息分类对应的getter方法   
  56.   
  57.     public Category getCategory() {   
  58.   
  59.         return category;   
  60.   
  61.     }   
  62.   
  63.     //消息分类对应的setter方法   
  64.   
  65.     public void setCategory(Category category) {   
  66.   
  67.         this.category = category;   
  68.   
  69.     }   
  70.   
  71.     //消息最后修改时间的getter方法   
  72.   
  73.     public Date getLastModifyDate() {   
  74.   
  75.         return lastModifyDate;   
  76.   
  77.     }   
  78.   
  79.     //消息最后修改时间的setter方法   
  80.   
  81.     public void setLastModifyDate(Date lastModifyDate) {   
  82.   
  83.         this.lastModifyDate = lastModifyDate;   
  84.   
  85.     }   
  86.   
  87.     //消息发布时间的getter方法   
  88.   
  89.     public Date getPostDate() {   
  90.   
  91.         return postDate;   
  92.   
  93.     }   
  94.   
  95.     //消息发布时间的setter方法   
  96.   
  97.     public void setPostDate(Date postDate) {   
  98.   
  99.         this.postDate = postDate;   
  100.   
  101.     }   
  102.   
  103.     //消息内容对应的getter方法   
  104.   
  105.     public String getContent() {   
  106.   
  107.         return content;   
  108.   
  109.     }   
  110.   
  111.     //消息发布者对应的setter方法   
  112.   
  113.     public void setContent(String content) {   
  114.   
  115.         this.content = content;   
  116.   
  117.     }   
  118.   
  119.     //消息主键对应的getter方法   
  120.   
  121.     public Long getId() {   
  122.   
  123.         return id;   
  124.   
  125.     }   
  126.   
  127.     //消息主键对应的setter方法   
  128.   
  129.     public void setId(Long id) {   
  130.   
  131.         this.id = id;   
  132.   
  133.     }   
  134.   
  135.     //消息标题对应的getter方法   
  136.   
  137.     public String getTitle() {   
  138.   
  139.         return title;   
  140.   
  141.     }   
  142.   
  143.     //消息标题对应的setter方法   
  144.   
  145.     public void setTitle(String title) {   
  146.   
  147.         this.title = title;   
  148.   
  149.     }   
  150.   
  151.     //Domain Object重写equals方法   
  152.   
  153.     public boolean equals(Object object) {   
  154.   
  155.         if (!(object instanceof News)) {   
  156.   
  157.             return false;   
  158.   
  159.         }   
  160.   
  161.         News rhs = (News) object;   
  162.   
  163.         return this.poster.equals(rhs.getPoster())   
  164.   
  165.                 && this.postDate.equals(rhs.getPostDate());   
  166.   
  167.     }   
  168.   
  169.     //Domain Object重写的hashCode方法   
  170.   
  171.     public int hashCode() {   
  172.   
  173.         return this.poster.hashCode() + this.postDate.hashCode();   
  174.   
  175.     }   
  176.   
  177.     //Domain Object重写toString方法   
  178.   
  179.     public String toString() {   
  180.   
  181.     return new ToStringBuilder(this).append("id"this.id).append("title",   
  182.   
  183.             this.title).append("postDate"this.postDate).append("content",   
  184.   
  185.             this.content).append("lastModifyDate"this.lastModifyDate)   
  186.   
  187.                 .append("poster"this.poster)   
  188.   
  189.                 .append("category"this.category).append("newsReviews",   
  190.   
  191.                         this.newsReviews).toString();   
  192.   
  193.     }   
  194.   
  195. }   
  196.   

第二个Domain Object是消息对应的回复,其代码如下:

java 代码
  1. public class NewsReview extends BaseObject   
  2.   
  3. {   
  4.   
  5.     //消息回复的主键   
  6.   
  7.     private Long id;   
  8.   
  9.     //消息回复的内容   
  10.   
  11.     private String content;   
  12.   
  13.     //消息回复的回复时间   
  14.   
  15.     private Date postDate;   
  16.   
  17.     //回复的最后修改时间   
  18.   
  19.     private Date lastModifyDate;   
  20.   
  21.     //回复的对应的消息   
  22.   
  23.     private News news;   
  24.   
  25.     //消息回复的构造器   
  26.   
  27.     public NewsReview() {   
  28.   
  29.     }   
  30.   
  31.     //回复内容对应的getter方法   
  32.   
  33.     public String getContent() {   
  34.   
  35.         return content;   
  36.   
  37.     }   
  38.   
  39.     //回复内容对应的setter方法   
  40.   
  41.     public void setContent(String content) {   
  42.   
  43.         this.content = content;   
  44.   
  45.     }   
  46.   
  47.     //回复主键对应的setter方法   
  48.   
  49.     public Long getId() {   
  50.   
  51.         return id;   
  52.   
  53.     }   
  54.   
  55.     //回复主键对应的setter方法   
  56.   
  57.     public void setId(Long id) {   
  58.   
  59.         this.id = id;   
  60.   
  61.     }   
  62.   
  63.     //回复的最后修改时间对应的getter方法   
  64.   
  65.     public Date getLastModifyDate() {   
  66.   
  67.         return lastModifyDate;   
  68.   
  69.     }   
  70.   
  71.     //回复的最后修改时间对应的setter方法   
  72.   
  73.     public void setLastModifyDate(Date lastModifyDate) {   
  74.   
  75.         this.lastModifyDate = lastModifyDate;   
  76.   
  77.     }   
  78.   
  79.     //回复对应的消息的getter方法   
  80.   
  81.     public News getNews() {   
  82.   
  83.         return news;   
  84.   
  85.     }   
  86.   
  87.     //回复对应的消息的setter方法   
  88.   
  89.     public void setNews(News news) {   
  90.   
  91.         this.news = news;   
  92.   
  93.     }   
  94.   
  95.     //回复发布时间的getter方法   
  96.   
  97.     public Date getPostDate() {   
  98.   
  99.         return postDate;   
  100.   
  101.     }   
  102.   
  103.     //回复发布时间的setter方法   
  104.   
  105.     public void setPostDate(Date postDate) {   
  106.   
  107.         this.postDate = postDate;   
  108.   
  109.     }   
  110.   
  111.     //Domain Object重写的equals方法   
  112.   
  113.     public boolean equals(Object object) {   
  114.   
  115.         if (!(object instanceof NewsReview)) {   
  116.   
  117.             return false;   
  118.   
  119.         }   
  120.   
  121.         NewsReview rhs = (NewsReview) object;   
  122.   
  123.         return this.poster.equals(rhs.getPoster()) &&    
  124.   
  125. this.postDate.equals(rhs.getPostDate());   
  126.   
  127.         /*return new EqualsBuilder().append(this.news, rhs.news).append(  
  128.  
  129.             this.content, rhs.content).append(this.postDate, rhs.postDate)  
  130.  
  131.                 .append(this.lastModifyDate, rhs.lastModifyDate).append(  
  132.  
  133.                         this.id, rhs.id).append(this.poster, rhs.poster)  
  134.  
  135.                 .isEquals();  
  136.  
  137. */  
  138.   
  139.     }   
  140.   
  141.     //Domain Object对应的hashCode方法   
  142.   
  143.     public int hashCode() {   
  144.   
  145.         return this.poster.hashCode() + this.postDate.hashCode();   
  146.   
  147.         /*return new HashCodeBuilder(-1152635115, 884310249).append(this.news)  
  148.  
  149.                 .append(this.content).append(this.postDate).append(  
  150.  
  151.                         this.lastModifyDate).append(this.id)  
  152.  
  153.                 .append(this.poster).toHashCode();  
  154.  
  155. */  
  156.   
  157.     }   
  158.   
  159.     //Domain Object对应的toString方法   
  160.   
  161.     public String toString() {   
  162.   
  163.         return new ToStringBuilder(this).append("id"this.id).append(   
  164.   
  165.             "postDate"this.postDate).append("lastModifyDate",   
  166.   
  167.                 this.lastModifyDate).append("content"this.content).append(   
  168.   
  169.                 "poster"this.poster).append("news"this.news).toString();   
  170.   
  171.     }   
  172.   
  173. }   
  174.   

从上面贫血模式的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对象:

java 代码
  1. //NewsDAOHibernate继承HibernateDaoSupport,实现NewsDAO接口   
  2.   
  3. public class NewsDAOHibernate extends HibernateDaoSupport implements NewsDAO   
  4.   
  5. {   
  6.   
  7.     //根据主键加载消息   
  8.   
  9.     public News getNews(Long id)    
  10.   
  11.     {   
  12.   
  13.           News news = (News) getHibernateTemplate().get(News.class, id);   
  14.   
  15.           if (news == null) {   
  16.   
  17.                throw new ObjectRetrievalFailureException(News.class, id);      
  18.   
  19.           }   
  20.   
  21.         return news;   
  22.   
  23.     }   
  24.   
  25.     //保存新的消息   
  26.   
  27.     public void saveNews(News news) {   
  28.   
  29.           getHibernateTemplate().saveOrUpdate(news);   
  30.   
  31.     }   
  32.   
  33.     //根据主键删除消息   
  34.   
  35.     public void removeNews(Long id)   
  36.   
  37.     {   
  38.   
  39.           getHibernateTemplate().delete(getNews(id));   
  40.   
  41.     }   
  42.   
  43.     //查找全部的消息   
  44.   
  45.     public List findAll()   
  46.   
  47.     {   
  48.   
  49.         getHibernateTemplate().find("from News"));   
  50.   
  51.     }   
  52.   
  53. }   
  54.   

既然DAO对象完成具体的持久化操作,因此基本的CRUD操作都应该在DAO对象中实现。但DAO对象应该包含多少个查询方法,并不是确定的。因此,根据业务逻辑的不同需要, 不同的DAO对象可能有数量不等的查询方法。

对于现实中News,应该包含一个业务方法(addNewsReviews方法)。在贫血模式下,News类的代码并没有包含该业务方法,只是将该业务方法放到业务逻辑对象中实现,下面是业务逻辑对象实现addNewsReviews的代码:

java 代码
  1. public class FacadeManagerImpl implements FacadeManager   
  2.   
  3. {   
  4.   
  5.     //业务逻辑对象依赖的DAO对象   
  6.   
  7.     private CategoryDAO categoryDAO;   
  8.   
  9.     private NewsDAO newsDAO;   
  10.   
  11.     private NewsReviewDAO newsReviewDAO;   
  12.   
  13.     private UserDAO userDAO;   
  14.   
  15.     //...此处还应该增加依赖注入DAO对象必需的setter方法   
  16.   
  17.     //...此处还应该增加其他业务逻辑方法   
  18.   
  19.     //下面是增加新闻回复的业务方法   
  20.   
  21.     public NewsReview addNewsReview(Long newsId , String content)   
  22.   
  23.     {   
  24.   
  25.         //根据新闻id加载新闻   
  26.   
  27.           News news = newsDao.getNews(newsId);   
  28.   
  29.         //以默认构造器创建新闻回复   
  30.   
  31.           NewsReview review = new NewsReview();   
  32.   
  33.         //设置新闻与新闻回复之间的关联   
  34.   
  35.           review.setNews(news);   
  36.   
  37.         //设置新闻回复的内容   
  38.   
  39.           review.setContent(content);   
  40.   
  41.         //设置回复的回复时间   
  42.   
  43.           review.setPostDate(new Date());   
  44.   
  45.         //设置新闻回复的最后修改时间   
  46.   
  47.           review.setLastModifyDate(new Date());   
  48.   
  49.         //保存回复   
  50.   
  51.           newsReviewDAO.saveNewsReview(review);   
  52.   
  53.           return review;   
  54.   
  55.     }   
  56.   
  57. }   
  58.   

在贫血模式下,业务逻辑对象正面封装了全部的业务逻辑方法,Web层仅与业务逻辑组件交互即可,无须访问底层的DAO对象。Spring的声明式事务管理将负责业务逻辑对象方法的事务性。

在贫血模式下,其分层非常清晰。Domain Object 并不具备领域对象的业务逻辑功能,仅仅是ORM框架持久化所需的POJO,仅是数据载体。贫血模型容易理解,开发便捷,但严重背离了面向对象的设计思想,所有的Domain Object并不是完整的Java对象。

总结起来,贫血模式存在如下缺点:

— 项目需要书写大量的贫血类,当然也可以借助某些工具自动生成。

—  Domain Object的业务逻辑得不到体现。由于业务逻辑对象的复杂度大大增加,许多不应该由业务逻辑对象实现的业务逻辑方法,完全由业务逻辑对象实现,从而使业务逻辑对象的实现类变得相当臃肿。

贫血模式的优点是:开发简单、分层清晰、架构明晰且不易混淆;所有的依赖都是单向依赖,解耦优秀。适合于初学者及对架构把握不十分清晰的开发团队。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics