三、行为型模式【模板模式、回调】

模板模式

主要是用来解决复用和扩展两个问题

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

模板模式作用一:复用

模板模式把一个算法中不变的流程抽象到父类的模板方法中,将可变的部分留给子类来实现。所有的子类都可以复用父类中模板方法定义的流程代码

  • Java InputStream
    read(byte b[], int off, int len) 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了 read() 由子类来定制的抽象方法。
public abstract class InputStream implements Closeable {
    
    
    //...省略其他代码...
    public int read(byte[] b, int off, int len) throws IOException {
    
    
        if (b == null) {
    
    
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
    
    
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
    
    
            return 0;
        }
        int c = read();
        if (c == -1) {
    
    
            return -1;
        }
        b[off] = (byte) c;
        int i = 1;
        try {
    
    
            for (; i < len; i++) {
    
    
                c = read();
                if (c == -1) {
    
    
                    break;
                }
                b[off + i] = (byte) c;
            }
        } catch (IOException ee) {
    
    
        }
        return i;
    }

    public abstract int read() throws IOException;
}


public class ByteArrayInputStream extends InputStream {
    
    
    //...省略其他代码...
    @Override
    public synchronized int read() {
    
    
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }
}
  • Java AbstractList
    addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportedOperationException 异常。前提是,如果子类不重写是不能使用的。
public abstract class AbstractList {
    
    
    public boolean addAll(int index, Collection<? extends E> c) {
    
    
        rangeCheckForAdd(index);
        boolean modified = false;
        for (E e : c) {
    
    
            add(index++, e);
            modified = true;
        }
        return modified;
    }

    public void add(int index, E element) {
    
    
        throw new UnsupportedOperationException();
    }
}

模板模式作用二:扩展

这里的扩展,并不是指代码的扩展性,而是指框架的扩展性。框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。

  • Java Servlet
    使用比较底层的 Servlet 来开发 Web 项目,只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。


    Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的 Servlet ,然后执行它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用
    doGet() 或 doPost() 方法,然后输出数据到网页。


    HttpServlet 的 service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost() 是模板中可以由子类来定制的部分。


    Servlet 框架提供了一个扩展点(doGet()、doPost() 方法),让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。

  • JUnit TestCase
    JUnit 框架也通过模板模式提供了一些功能扩展点(setUp()、tearDown() 等),让框架用户可以在这些扩展点上扩展功能。


    在使用 JUnit 测试框架来编写单元测试的时候,编写的测试类都要继承框架提供的 TestCase 类。在 TestCase 类中,runBare() 函数是模板方法,它定义了执行测试用例的整体流程:先执行 setUp()
    做些准备工作,然后执行 runTest() 运行真正的测试代码,最后执行 tearDown() 做扫尾工作。

回调

相对于普通的函数调用,回调是一种双向调用关系。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.");
            }
        });
    }
}
  • 异步回调指的是在函数返回之后执行回调函数

    通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。

  • Spring JdbcTemplate
    JdbcTemplate,对 JDBC 进一步封装,来简化数据库编程。使用 JdbcTemplate 查询用户信息,只需要编写跟这个业务有关的代码,其中包括,查询用户的 SQL 语句、查询结果与 User
    对象之间的映射关系。其他流程性质的代码(加载驱动、创建数据库连接、创建 statement、关闭连接、关闭 statement、处理异常)都封装在了 JdbcTemplate 类中,不需要我们每次都重新编写。

public class JdbcTemplateDemo {
    
    
    private JdbcTemplate 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;
        }
    }
}

//  JdbcTemplate 通过回调的机制,将不变的执行流程抽离出来,放到模板方法 execute() 中,将可变的部分设计成回调 StatementCallback,由用户来定制。query() 函数是对 execute() 函数的二次封装,让接口用起来更加方便。


模板模式 VS 回调

  • 应用场景

    同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。


    异步回调跟模板模式有较大差别,更像是观察者模式。

    扫描二维码关注公众号,回复: 16754652 查看本文章
  • 代码实现

    回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系


    模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

  • 组合优于继承!在代码实现上,回调相对于模板模式会更加灵活

    1、 Java 只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。

    2、回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。

    3、如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。

猜你喜欢

转载自blog.csdn.net/weixin_46488959/article/details/126919274