模板方法与回调函数

模板方法定义

  1. 模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”。
  2. 在模板模式经典的实现中,模板方法定义为final,可以避免被子类重写。需要子类重写的方法定义为abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。
  3. 模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。有点控制反转的思想;

JAVA中运用模板方法的实例

  1. JUC包中AbstractQueuedSynchronizer(AQS)大量运用了模板方法的设计模式,如acquire方法:
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

每个继承AQS的类只需要在tryAcquire()方法中实现自己特定的获取锁逻辑就可以了;
而重试、进入CLH等待队列、挂起、中断、唤醒等等的操作对于每一个类其实都是一样的,所以他们都封装在acquireQueued()方法中了,其他类继承AQS重写tryAcquire即可,也就达到了代码复用的目的;

2. Java Servlet:大量可复用的代码都在HttpServlet的service()函数中是处理了,每个继承HttpServlet的子类只需要重写doGet()或doPost()方法,来分别处理get和post请求即可;这也是模板方法的一种体现;
3. JDK以及很多工具框架都运用到了模板方法,如Java InputStream类、JUnit TestCase、Java AbstractList等等等等;

回调函数定义

  1. 复用和扩展是模板模式的两大作用,实际上,还有另外一个技术概念,也能起到跟模板模式相同的作用,那就是回调(Callback)
  2. 回调是一种双向调用关系。A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。这里的F函数就是“回调函数”。A调用B,B反过来又调用A,这种调用机制就叫作“回调”。
public interface ICallback {
  void methodToCallback();
}

public class BClass {
  public void process(ICallback callback) {
    //...
    callback.methodToCallback();
    //...
  }
}

public class AClass {
  public static void main(String[] args) {
    BClass b = new BClass();
    b.process(new ICallback() { //回调对象
      @Override
      public void methodToCallback() {
        System.out.println("Call back me.");
      }
    });
  }
}
  1. 回调不仅可以应用在代码设计上,在更高层次的架构设计上也比较常用。比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。亦或者和第三方做数据打通时,甲系统往乙系统推送了数据,但是甲并不知道最终有没有推送成功,那就可以在推送数据的时候携带一个回调URL,当乙接受到甲推送的数据并成功处理后就回调这个URL来通知甲。当然乙回调甲提供的URL也可能失败,这就需要重试机制或者定时补偿机制来处理了。

Spring中运用回调机制的实例

  1. Spring提供了很多Template类,比如,JdbcTemplate、RedisTemplate、RestTemplate。尽管都叫作xxxTemplate,但它们并非基于模板模式来实现的,而是基于回调来实现的,确切地说应该是同步回调。而同步回调从应用场景上很像模板模式,所以,在命名上,这些类使用Template(模板)这个单词作为后缀。
  2. 如Spring提供的JdbcTemplate,把跟业务无关比如,加载驱动、创建数据库连接、创建statement、关闭连接、关闭statement、处理异常等等代码都封装起来了以供复用。这也很像上面说到的的模板方法,都是对于重复的代码进行封装复用。
  3. JdbcTemplate开发常用示例如下:
public User queryUser(long id) {
    String sql = "select * from user where id="+id;
    return jdbcTemplate.query(sql, new UserRowMapper()).get(0);
  }

  class UserRowMapper implements RowMapper<User> {
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
      User user = new User();
      user.setId(rs.getLong("id"));
      user.setName(rs.getString("name"));
      user.setTelephone(rs.getString("telephone"));
      return user;
    }
  }
  1. 而JdbcTemplate的主要工作便是把执行sql语句以及返回结果映射类型封装成一个回调函数来执行,这样便可以在jdbc的流程操作(加载驱动、创建连接等等)中嵌入执行这个回调函数,以达到对于各种各样的sql的语句和结果映射的支持;
  2. 具体封装源码有兴趣的话可以看下这几个函数方法的实现:
//query()函数把sql函数和返回结果映射关系
//封装成StatementCallback回调对象,最终传给execute()方法调用。
//可以认为query()函数是对execute()函数的二次封装,让接口用起来更加方便
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException

//最终执行函数,当重复的流程走完后,便会回调action.doInStatement()方法执行sql语句封装返回结果
public <T> T execute(StatementCallback<T> action) throws DataAccessException 

模板模式 VS 回调

  1. 应用场景上,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。
  2. 代码实现上,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
  3. 组合优于继承,在代码实现上,回调相对于模板模式会更加灵活,主要体现在下面几点。
    1. 像Java这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
    2. 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
    3. 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。
  4. 当然模板模式也并非一无是处,当扩展点非常多的时候,模板模式可以给出很多扩展点的默认实现,使用者可以自由选择要扩展的点。而如果使用回调函数来处理多扩展点的情况,那么调用方需要传入的参数会非常多,封装的回调函数也会过多,编码也会更加复杂;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值