网站内容管理系统

  作者:板桥banq

上页

3.4 生成器(Builder)模式

本项目关键功能是将页面内容和模板有机地混合在一起,这个混合过程实际是一种组装创建的过程,依据不同的模板技术会有不同的生成组装过程。
本项目设计中采取的是Tile模板技术,一个页面由几个区域的JSP文件组成,其中关键部分有两点:
将菜单(Menu)数据和菜单显示JSP 结合在一起。
将内容(Body)数据和内容显示JSP结合在一起。
这种具体实现细节有很多种办法,每个办法都有一定的优点和缺点,其中有一个最容易实现的方案:
在菜单JSP中动态访问Navlink中的Menu,将Menu对象遍历后输出。
在内容JSP中读取Body的Xml数据,由于内容部分可能包含大量HTML,而且数据可能庞大,再采取一个简单变通的办法,直接将这些包含HTML的内容存储为内容JSP文件,那么原来Body对象实际变成了有关内容属性的对象。
上述方案只是众多方案中的一种,假设该方案叫Standard方案,如果以后有更好的算法方案,应该能方便地替换Standard方案,这时就需要有一个可替换的机制。将Standard方案封装起来就可以实现替换,生成器Builder模式能够实现这种组装过程的封装。
Builder模式是一种将复杂对象的组装创建过程封装起来的模式,将过程中具体实现细节隐含在抽象接口后面。想到Builder模式应该就会联想到汽车的组装:汽车分很多部件,部件组装起来就成为一辆汽车,Builder模式是将汽车组装过程进行了封装。
“页”是这个项目中的一个复杂对象,需要通过复杂的混合组装过程,将内容在一个“页”中以一定模板显示出来。分别依据Builder模式的参与者设计如下:
“页(Page)” 是Builder模式中要构造的复杂对象。“页(Page)”是由菜单(Menu)、内容(body)以及模板JSP等部件组成。
Builder模式中生成器是部件创建的抽象接口,在这个接口中,定义部件创建的一些方法:
/**
 * Builder模式接口
 * <p>Copyright: Jdon.com Copyright (c) 2003</p>
 * <p>Company: 上海汲道计算机技术有限公司</p>
 * @author banq
 * @version 1.0
 */
public interface PageBuilder {
    //创建Menu部件的方法
    public Menu buildMenu(Integer Id);
    //创建Content部件的方法
    public String buildContent(Integer Id);
    //创建Body部件的方法
    public Body buildBody(Integer Id);
    //创建模板JSP部件的方法
    public boolean buildTemplate(Integer Id);
    //获得组装构建后的产品:“页(Page)”
    public Page getPage();
}
在对“页(Page)”有了准确的部件分解后,才会有PageBuilder中抽象的部件构建方法。不管以后如何变化,Page组成的部件是不会变化的。正是有这个不变的前提,才能封装部件的创建以及部件之间组合的过程。
Builder模式中的Director 是指导部件的组装,实现与外界的接口:
/**
 * Builder模式Director
 * <p>Copyright: Jdon.com Copyright (c) 2003</p>
 * <p>Company: 上海汲道计算机技术有限公司</p>
 * @author banq
 * @version 1.0
 */
public class PageRender {

  private PageBuilder pageBuilder = null;
  //构造PageRender类时需要指定一个生成器
  public PageRender(PageBuilder pageBuilder){
    this.pageBuilder = pageBuilder;
  }

  //创建组构造页面,该方法是被外界访问和调用的
  public boolean construct(Integer Id) {
     //执行菜单Menu组装
     Menu menu = pageBuilder.buildMenu(Id);
     //执行Content组装
     String output = pageBuilder.buildContent(Id);
     //执行Body组装
     Body body = pageBuilder.buildBody(Id);
     //执行模板组装
     if (pageBuilder.buildTemplate(Id)){
       Debug.logVerbose(" -->>>finish constructing a new page", module);
       return true;
     }else{
       return false;
     }

  }

 

   //解构页面方法,如果页面需要被删除
   //需要逐步删除或撤除这些部件
   public void unConstruct(Integer Id) {
      //首先删除模板 这与创建时顺序相反
      pageBuilder.buildTemplate(Id);
      //删除Content
      pageBuilder.buildContent(Id);
      //删除Body
      pageBuilder.buildBody(Id);
      //最后删除菜单
      pageBuilder.buildMenu(Id);
   }
}
上例中,已经看到Director如何指挥一个Page产品的组装和拆除过程。组装的过程是有先后次序的:菜单总是需要第一个被创建,有了菜单,才有菜单指向的内容,内容有了,才会有页面的模板显示。而删除页面时,整个次序则倒过来,从外向里逐个剥离开。
设计好3个主要的参与者,Builder模式基本确立,还有一个是参与者是生成器(Builder)的具体实现者。
StandardPageBuilder是PageBuilder的具体实现者。在这个类中,封装的是Standard方案。如果将来有其他更好的实现细节,可以再创建一个类。比如BestPageBuilder,使用BestPageBuilder代替StandardPageBuilder就可以使系统有一个更好的算法,而这种切换带来的修改是非常小的。从外界调用的变化中可以看出:
采取Standard方案时外界的调用如下:
  PageBuilder pageBuilder = new StandardPageBuilder(pageEvent);
  PageRender render = new PageRender(pageBuilder);
  render.construct(Id);
采取Best方案后的调用如下:
  PageBuilder pageBuilder = new BestPageuilder (pageEvent);
  PageRender render = new PageRender(pageBuilder);
  render.construct(Id);
可见变化的只是一个实例名的更改。
StandardPageBuilder代码的主要部分如下:
public class StandardPageBuilder implements PageBuilder {
  private final static String module = StandardPageBuilder.class.getName();
  private final static XmlUtil xmlUtil = XmlUtil.getInstance();
  //初始化PageFactory
  private final static PageFactory pageFactory = PageFactory.getInstance();

  private Page page = null;
  private String action = null;
  public StandardPageBuilder(PageEvent pageEvent) {
    this.page = pageEvent.getPage();
    this.action = pageEvent.getAction();
  }
   /**
   * 生成Menu
   */
  public Menu buildMenu(Integer Id) {
    if (action.equals(PageEvent.CREATE))
      return createMenu(Id);
    else if (action.equals(PageEvent.QUERY))
      return queryMenu(Id);
    else if (action.equals(PageEvent.EDIT))
      return updateMenu(Id);
    else if (action.equals(PageEvent.DELETE))
      deleteMenu(Id);
    return null;
  }
  /**
   * 1. 创建新的Menu实例
   * 2. 保存到持久层
   */
  private Menu createMenu(Integer Id) {
    Debug.logVerbose(" -->> enter build menu:" + page.getName(), module);
    Menu menu = pageFactory.createMenu(Id);
    pageFactory.updateMenu(menu, page);
    Debug.logVerbose("menu object created, ID is " + menu.getId(), module);
    Navlink navlink = page.getNavlink();
    navlink.setId(Id);
    //add the menu to the navlink
    navlink.addMenu(menu);
    try {
      //委托pageFactory实现存储操作
      pageFactory.saveNavlink(navlink);
      Debug.logVerbose(" --> >build Menu ok", module);
      page.setNavlink(navlink);
    } catch (Exception ex) {
      Debug.logError(" build Menu error:" + ex, module);
      menu = null;
    }
    return menu;
  }
  …
  //生成Body
  public Body buildBody(Integer Id) {
    if (action.equals(PageEvent.CREATE))
      return createBody(Id);
    else if (action.equals(PageEvent.QUERY))
      return queryBody(Id);
    else if (action.equals(PageEvent.EDIT))
      return updateBody(Id);
    else if (action.equals(PageEvent.DELETE))
      deleteBody(Id);
    return null;
  }
  //委托PageFactory实现Body数据的创建
  private Body createBody(Integer Id) {
    Debug.logVerbose(" -->> enter build Body " + page.getTitle(), module);
    String author = "";
    String title = page.getTitle();
    //body's content is the file name that contains the content of the page
    String content = page.getHtmlText();
    Integer hits = new Integer(0);
    Integer status = new Integer(0);
    Body body = null;
    try {
      body = pageFactory.createBody(Id);
      body.setAuthor(author);
      body.setTitle(title);
      body.setContent(content);
      body.setHits(hits);
      body.setStatus(status);
      pageFactory.updateBody(body);
    } catch (Exception e) {
      Debug.logError(" build body error in :" + e);
    }
    return body;
  }
 …
 //生成模板
 public boolean buildTemplate(Integer Id) {
    if (action.equals(PageEvent.CREATE))
      return createTemplate(Id);
    else if (action.equals(PageEvent.EDIT))
      return createTemplate(Id);
    else if (action.equals(PageEvent.DELETE))
      deleteTemplate(Id);
    return true;
  }
  //创建模板文件xxx.jsp
  public boolean createTemplate(Integer Id) {
    boolean success = false;
    Menu menu = null;
    try {
      Debug.logVerbose("--> find the menu by the Id: " + Id, module);
      //从PageFactory获取Menu对象
      menu = pageFactory.getMenu(Id, page.getNavlink());
      if (menu == null)
        return success;
      String titleValue = menu.getName();
      String bodyValue = menu.getDataLink();
      Debug.logVerbose("--> jspFileName =" + menu.getLink() + " bodyValue=" +
                       bodyValue, module);
     
      String jspPathName = XmlUtil.getJspDirStr(menu.getId().toString());
      //jsp文件操作实际委托JspMaker操作
      success = JspMaker.generate(jspPathName, titleValue, bodyValue);
    } catch (Exception ex) {
      Debug.logError(" nof get the menu id =" + Id, module);
      return success;
    }
    return success;
  }
  …
}
在图4-10中,PageHandler作为Builder模式的客户端,通过使用PageRender的construct访问来构建页面产品。而StandardPageBuilder作为Builder接口的一个实现,它又将有关Menu Body等对象的操作委托给PageFactory来完成。
结合图4-10和图4-9,本项目的整个流程控制图基本已经明晰,用户向表现层发出请求,在表现层封装成基本对象后,交由图4-10中的PageHandler处理,而PageHandler将复杂的组装过程委托给Builder模式来实现。在采取Standard方案组装“页(page)”的StandardPageBuilder中,凡是涉及到数据层操作,例如数据持久化保存等,都再次委托给PageFactory去处理。
1
图4-10  Builder模式的流程图
通过图4-9,PageFactory封装了数据层的操作细节,其中一个是使用XML文件作为数据保存的方案XmlPageFactory。XmlPageFactory又将各个部件对象的数据操作委托NavlinkManager或BodyManager这样的类来实现。在NavlinkManager中调用DBO模式的实现者CastorHandler,而在CastorHandler中封装的是数据对象序列化或反序列化的细节和相关XML API操作细节。
本项目的整体设计主要体现了模式驱动的架构设计,在整个流程图中,每个模式很好地实现和封装了项目需求的各个功能,而且每个模式又层层相扣且紧密衔接在一起,就如同模板有机地拼装在一起。更重要的是,这样一个系统是一个具有很强伸缩性的动态可扩展的、稳健的系统,而且便于他人阅读理解。
假设不采取这样的模式驱动思路来设计,这个系统将会怎样?首先,多层架构分离就无法很好地实现,这样,数据持久化操作也许可能就在表现层中完成;其次,菜单或内容对象的操作以及组装细节可能混合充斥在一两个类中实现,按照这样的过程化设计思路编制后的系统将是怎样?也许可以运行,也许调试时就会出现错误无法定位的问题,难于调试,更加难于维护和拓展。
更有甚者,如果有一天需要使用数据库来实现菜单或内容对象的数据持久化,整个系统就会重建。重建的代价实际是破坏了系统的稳定性,客户的抱怨会严重影响公司的声誉。 
所有这些代价的付出都是由于设计的简单性,其中痛苦和无奈是很多有经验的程序员经常碰到的。

 

下页