设计模式之模板方法

来源:互联网 发布:围棋软件形势判断 编辑:程序博客网 时间:2024/04/30 09:06

定义:模板方法模式(Template Method),定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

结构图:


角色及其职责

1AbstractClass:是抽象类,其实也就是一个抽象模板,定义并实现了一个模板方法,这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级也有可能调用一些具体方法。

2ConcreteClass:实现父类所定义的一个或多个抽象方法。每一个AbstractClass都有任意多个ConcreteClass与之对应,而每个ConcreteClass都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

使用场景

1)       JUnit中的TestCase以及它的子类就是一个模板方法模式的例子。在TestCase这个抽象类中将整个测试的流程设置好,比如先执行setup方法初始化测试资源,再运行测试方法,然后再tearDown来释放测试资源。但是我们将在setup、tearDown里面作些什么?谁知道呢?!因此,这些步骤的具体实现都会延迟到子类中去,也就是我们实现的测试类中。

2)       Struts框架控制器的核心类RequestProcess里面的process方法也是采用了经典的模板方法模式,里面定义好了流程的步骤,而步骤里面的很多环节,我们都是可以重写的。

3)       Spring用另一种方式实现了Template Method模式,我们来详细解读一下。Spring对Hibernate的调用提供了HibernateDaoSupport的支持,在该类中,有了一个HibernateTemplate类来调用Hiebarnate接口,通过另外一种方式使用Template Method模式,避免了开发者代码中出现诸如Session管理、事务控制等重复代码。

具体实例:

现在我们看看Spring是如何做Template的,打开spring源代码发现在HibernateTemplate中包含execute(HibernateCallback action);该方法扮演的角色就是类似与上图中的AbstractClass类中的TemplateMothod.而该方法有只有一个唯一参数就是接口HibernateCallback所引用的对象action,在改接口抽象出了变化的数据库操作:

public interface HibernateCallback {

    Object doInHibernate(Sessionsession) throws HibernateException, SQLException;

}

就像我们在前面形容的一样HibernateCallback扮演一个“钩子(hook)“的角色,这样用接口和不同的接口实现类替代了Template Method模式中的继承关系同时也将模板方法execute(HibernateCallback action)独立出来。

看看execute的实现:

public Object execute(HibernateCallback action, boolean exposeNativeSession) throws DataAccessException {

       Sessionsession = getSession();

boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session,getSessionFactory());

       if (existingTransaction) {

                          logger.debug("Foundthread-bound Session for HibernateTemplate");

       }

       FlushModepreviousFlushMode = null;

       try {

          previousFlushMode = applyFlushMode(session, existingTransaction);

     SessionsessionToExpose = (exposeNativeSession ? session :createSessionProxy(session));

          Object result = action.doInHibernate(sessionToExpose);

          flushIfNecessary(session, existingTransaction);

           return result;

       }

       catch (HibernateException ex) {

           throw convertHibernateAccessException(ex);

       }

       catch (SQLException ex) {

           throw convertJdbcAccessException(ex);

       }

       catch (RuntimeException ex) {

           // Callback code threw application exception...

           throw ex;

       }

       finally {

           if (existingTransaction) {

              logger.debug("Not closingpre-bound Hibernate Session after HibernateTemplate");

              if (previousFlushMode != null) {

                 session.setFlushMode(previousFlushMode);

             }

          }

           else {

             SessionFactoryUtils.releaseSession(session, getSessionFactory());

          }

       }

    }

 

我们可以看到,在excute方法中做了对Session的管理和事务的控制,而代码:

Object result =action.doInHibernate(sessionToExpose);

则执行实际的数据库操作。

我们看看HibernateTemplate对hibernate的一个get方法的封装:

    public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)

           throws DataAccessException {

return execute(new HibernateCallback() {

                                public Object doInHibernate(Session session) throws HibernateException {

                             if (lockMode != null) {

                                    return session.get(entityClass, id, lockMode);

                           }

                            else {

                            return session.get(entityClass, id);

                           }

                    }

              }, true);

       }

在该段代码中用匿名类声明了一个接口实现,并将改对象作为参数传给excute方法。如此,便实现了Template Method设计模式。

从整体来看,我觉得有几点可以值得关注:

1.       用接口代替继承来抽象出hibernate的不同数据库操作,达到一种更加轻量级的效果

2.       将对变动的与不变的相互分离,在本例中不变的是Session和事务管理,变动的是不同的数据库操作

3.       通过封装隐藏Template Method模式,对用户的使用是透明的

这样就是一种更优雅的方式实现了TemplateMethod模式。

总结:

1.       模板方法设计模式的意图

通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序。但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关。

模板方法模式把我们不知道具体实现的步骤封装成抽象方法,提供一个按正确顺序调用它们的具体方法(这些具体方法统称为“模板方法”),这样构成一个抽象基类。子类通过继承这个抽象基类去实现各个步骤的抽象方法,而工作流程却由父类控制。

2.       模板方法模式定义及结构

模板方法模式属于行为模式的一种(GOF95)。准备一个抽象类,定义一个操作中的算法的骨架,将一些步骤声明为抽象方法迫使子类去实现。不同的子类可以以不同的方式实现这些抽象方法。

3.       模板方法模式与控制反转

不要给我们打电话,我们会给你打电话”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,这就是控制反转。子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

注意:模板方法模式中,抽象类的模板方法应该声明为final的。因为子类不能覆写被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。

4.       模板方法模式与开闭原则

什么是“开闭原则”?

开闭原则是指一个软件实体应该对扩展开放,对修改关闭。也就是说软件实体必须是在不被修改的情况下被扩展。模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。

父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。同时,因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。

5.       模板方法模式与对象的封装性

面向对象的三大特性:继承,封装,多态。

对象有内部状态和外部的行为。封装是为了信息隐藏,通过封装来维护对象内部数据的完整性。使得外部对象不能够直接访问一个对象的内部状态,而必须通过恰当的方法才能访问。

在Java中,采用给对象属性和方法赋予指定的修改符(public、protected、private)来达到封装的目的,使得数据不被外部对象恶意的访问及方法不被错误调用导造成破坏对象的封装性。

注意:模板方法模式中,迫使子类实现的抽象方法应该声明为protected abstract。

6.       模板方法和其他模式的联系

各个模式之间都有联系,模板方法也不例外,她并不是孤立存在的。模板中的那些虚方法实际上都是使用工厂方法设计模式,将父类的执行逻辑延迟到子类。有的时候模板方法里定义算法的步骤会用到策略模式,因为有的时候这个算法不止一种,比如上面的教育部规定新生报到流程这个算法,有可能教育部规定了三四种,那么我们就可以用策略模式封装这几套算法。

7.       模板方法的变形

学习设计模式最忌讳的就是生搬硬套,我们上面给出的Spring的HibernateTemplate源码分析就是告诉大家设计模式一定要领会精髓,活学活用。

原创粉丝点击