技术开发 频道

模板方法模式实现探讨



    6.模板方法模式与勾子方法(hookMethod)
    上面讨论模板方法模式运用于一个业务对象。事实上,框架频繁使用模板方法模式,使得框架实现对关键逻辑的集中控制。
思考这样的一个需求:我们需要为基本Spring的应用做一个测试类的基类,用于对类的方法进行单元测试。我们知道Spring应用把需要关联的对象都定义在外部的xml文件中,也称为context。通常我们会把context分割成多个小的文件,以便于管理。在测试时我们需要读取context文件,但是并不是每次都读取所有的文件。读取这些文件及实例化context中的对象是很费时间的。所以我们想把它缓存起来,只要这个文件被读取过一次,我们就把它们缓存起来。我们通过扩展Junit的TestCase类来定义一个测试基类。我们需要在这个基类中实现缓存的逻辑,其它开发人员只需要实现读取配置文件的方法即可。它不用管是否具有缓存功能。

public AbstractCacheContextTests extends TestCase{ private static Map contextMap = new HashMap(); protected ConfigurableApplicationContext applicationContext; protected boolean hasCachedContext(Object contextKey) { return contextKeyToContextMap.containsKey(contextKey); } protected ConfigurableApplicationContext getContext(Object key) { String keyString = contextKeyString(key); ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) contextKeyToContextMap.get(keyString); if (ctx == null) { if (key instanceof String[]) { ctx = loadContextLocations((String[]) key); } contextKeyToContextMap.put(keyString, ctx); } return ctx; } protected String contextKeyString(Object contextKey) { if (contextKey instanceof String[]) { return StringUtils.arrayToCommaDelimitedString((String[]) contextKey); } else { return contextKey.toString(); } } protected ConfigurableApplicationContext loadContextLocations(String[] locations) { return new ClassPathXmlApplicationContext(locations); } //覆写TestCase的setUp方法,在运行测试方法之前从缓存中读取context文件,如果缓存中不存在则初始化applicationContext,并放入缓存. protected final void setUp() throws Exception { String[] contextFiles = getConfigLocations(); applicationContext = getContext(contextFiles); } //读取context文件,由子类实现 protected abstract String[] getConfigLocations(); }
    这个基类的子类只需要去实现getConfigLocations方法,提供需要读取的配置文件字符数组即可。至于怎么去读取context文件内容,怎么实现缓存,则无需关心。
     AbstractCacheContextTests保证在运行所有测试方法之前去执行读取context文件的动作。
    注意,这里setUp方法被声明为protected,是因为setUp方法是TestCase类的方法。在这里setUp方法被定义为final了,确保子类不能去覆写这个方法,从而保证了父类控制的逻辑。

    在这里我们就会发现一个问题了,如果你使用过Junit你就会明白发生了什么问题。TestCase的setUp方法,就是在这个测试类的测试方法运行之前作一些初始化动作。如创建一些所有测试方法都要用到的公共对象等。我们在这里把setUp方法声明为final之后,子类再也无法去扩展它了,大多数情况下,子类同时还需要一些额外的初始化动作。

    能你会说:“把setUp方法的final修饰符去掉就可以了啊”。这样是可以的,但是去掉final修饰符后,子类可以覆写setUp方法,而去执行一些额外的初始化。噩梦发生了,父类可能从此失去了必须读取context文件及缓存context的功能。
为了解决这个问题,我们实现一个空方法onSetUp。在setUp方法中调用onSetUp方法。这样子类就可以通过覆写onSetUp方法来进行额外的初始化。

    //覆写TestCase的setUp方法,在运行测试方法之前从缓存中读取context文件,如果缓存中不存在则初始化applicationContext,并放入缓存.
protected final void setUp() throws Exception { String[] contextFiles = getConfigLocations(); applicationContext = getContext(contextFiles); onSetUp(); } protected void onSetUp(){ } //读取context文件,由子类实现 protected abstract String[] getConfigLocations(); }
    为什么不把onSetUp声明为abstract呢?这是因为子类不一定总是需要覆写onSetUp方法。onSetUp方法是为了对setUp方法的扩展。
    而像onSetUp这样的空方法就称之为勾子方法(HookMethod);
0
相关文章