模板方法《漫谈设计模式》阅读笔记
模板方法
春节回家,我们首先需要购买火车票,然后乘坐火车,最后才能和家人团聚。我们来模拟一下这个过程:
Public class HappyPeople{
Public void celebrateSpringFestival(){
System.out.println("Buying ticket");
System.out.println("Travelling by train");
System.out.println("Happy Chinese New Year");
}
}
后来我们发现,有人需要坐火车回家,有人需要坐飞机回家,而有人坐大巴回家。但是不管你乘坐哪种交通工具回家,都得先买票,然后才能和家里人团聚。于是我们又创建一个新类来实现坐汽车回家的逻辑:
Public class PassengerByCoach{
Public void celebrateSpringFestival(){
System.out.println("Buying ticket");
System.out.println("Travelling by bus");
System.out.println("Happy Chinese New Year");
}
}
同样,坐飞机回家又要复制粘贴多一份代码修改一下,我们会想,这样做真的合理吗?很显然这是不够合理的。
我们知道,为了重复使用代码,可以使用OOP一大特性----继承。上述两个类订票和团聚的逻辑是相同的,只有乘坐的方法不同,因此我们可以在子类中实现各自回家的方式。根据分析我们画出UML静态类图:
代码实现:
首先我们实现父类HappyPeople,代码如下
Public abstract class HappyPeople{
Public void celebrateSpringFestival(){
subscribeTicket();
travel();
celebrate();
}
Protected final void subscribe Ticket(){
//...
}
Protected abstract void travel();
Protected final void celebrate(){
//....
}
}
坐飞机子类:
Public class PassengerByAir extends HappyPeople{
@Override
Protected void travel(){
//Travelling by air
System.out.println("Travelling by air");
}
}
其它乘坐方式类似。
使用继承,子类中不需要实现那些重复的订票和庆祝团圆的代码了,避免了代码的重复。
模板方法模式:
回顾上述代码,父类中的方法celebrateSpringFestival()是我们的一个模板方法,它把回家过年分为三步,其中方法travel()是抽象部分,用于子类实现不同客户化逻辑,我们所使用的正是模板方法模式。
GOF给出的模板方法模式的定义是:定义了在一个操作中的一个算法框架,把一些步骤推迟到子类去实现。模板方法模式让子类不需要改变算法结构而重新定义特定的算法步骤。
也就是说模板方法定义了一系列算法步骤,子类可以去实现/覆盖其中某些步骤,但不能改变这些步骤的执行顺序。
引入回调:
模板方法模式的应用很广泛,但过分地使用模板方法模式,往往会引起子类的泛滥。比如查询数据库,首先我们需要得到数据库连接Connection对象,然后创建Statement实例并执行相关的查询语句,最后处理查询出来的结果并在整个执行过程中处理异常。研究这些步骤,整个过程中的第一步,第二步以及异常处理的逻辑对于每次查询来说都是相同的,发生改变的部分主要是在对查询结果的处理上。据上分析,模板方法模式很适合处理这个问题,我们只需要抽象出这个处理查询结果的方法供不同的子类去延迟实现即可。
如果真的这样做,你就会慢慢发现,由于各种各样的查询太多,导致我们需要创建的很多子类来处理这些查询结果,引起了子类的泛滥。为了解决此问题,通常我们结合使用回调来处理这种问题。
回调表示一段可执行逻辑的引用,我们把该引用传递到另一段逻辑(或者方法)里供这段逻辑适时调用。回调在Java中通常使用匿名内部类来实现。
为了实现上述数据库的查询,我们设计了SimpleJdbcQueryTemplate为模板方法类,ResultSetHandler为回调接口,如下UML静态类图所示:
客户对象使用SimpleJdbcQueryTemplate类的query(String queryString,ResultSetHandler rsHandler)方式时,需要把回调作为第二个参数传递给这个方法,query(...)方法会在做完查询后执行该回调的handle(Result rs)方法处理查询,返回最后的处理结果。
代码实现:
Public interface ResultSetHandler<T>{
Public T handle(Result rs);
}
Public class SimpleJdbcQueryTemplate{
Public <T> T query(String queryString,ResultSetHandler<T> rsHandler){
Try{
connection=getCollection();
stmt=connection.prepareStatement(queryString);
resultSet rs=stmt.executeQuery();
return rsHandler.handle(rs);
}catch(SQLException ex){
//handle exceptions..
}finally{
//close the statement&connection..
}
}
}
测试大致代码:
Public void testTemplate(){
Boolean called= new SimpleJdbcQueryTemplate().query("select * from db",
new ResultSetHandle<Boolean>(){
Public Boolean handle(Result rs){
//logical to resolve query result
return Boolean.TRUE;
}
});
assertTrue(called);
}
我们结合使用了模板方法模式和回调,避免了类的泛滥,此模式在Spring框架里使用十分广泛。