如何将Java模板模式应用于长尾词处理?

2026-04-19 12:271阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3552个文字,预计阅读时间需要15分钟。

如何将Java模板模式应用于长尾词处理?

模板模式是一种设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中。英文全称是 Template Method Design Pattern。这里的算法可以理解为广义上的业务逻辑,它不特指数据结构和具体算法实现。而算法框架则是指提供算法基本步骤和结构的模板,由子类具体实现细节。

模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。

这里的“算法”,可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

代码实现示例,如下所示。templateMethod() 函数定义为 final,是为了避免子类重写它。method1() 和 method2() 定义为 abstract,是为了强迫子类去实现。不过,这些都不是必须的,在实际的项目开发中,模板模式的代码实现比较灵活。

创建一个抽象类,定义算法的骨架:

public abstract class AbstractTemplate { // 模板方法,定义算法的骨架 public final void templateMethod() { step1(); step2(); step3(); } // 基本方法,定义算法中不会变化的步骤 private void step1() { System.out.println("Step 1: Prepare the ingredients."); } // 抽象方法,定义算法中需要子类实现的步骤 protected abstract void step2(); // 基本方法,定义算法中不会变化的步骤 private void step3() { System.out.println("Step 3: Serve the dish."); } }

创建具体的子类,实现抽象类中定义的抽象方法:

public class ConcreteTemplateA extends AbstractTemplate { @Override protected void step2() { System.out.println("Step 2 (A): Cook the dish using method A."); } } public class ConcreteTemplateB extends AbstractTemplate { @Override protected void step2() { System.out.println("Step 2 (B): Cook the dish using method B."); } }

在客户端代码中使用模板方法:

public class Main { public static void main(String[] args) { AbstractTemplate templateA = new ConcreteTemplateA(); AbstractTemplate templateB = new ConcreteTemplateB(); System.out.println("Using Template A:"); templateA.templateMethod(); System.out.println("\nUsing Template B:"); templateB.templateMethod(); } }

运行上面的程序,输出如下:

vbnetCopy code Using Template A: Step 1: Prepare the ingredients. Step 2 (A): Cook the dish using method A. Step 3: Serve the dish. Using Template B: Step 1: Prepare the ingredients. Step 2 (B): Cook the dish using method B. Step 3: Serve the dish.

AbstractTemplate是一个抽象类,它定义了一个名为templateMethod的模板方法。该方法包含三个步骤:step1step2step3。其中,step1step3是基本方法,它们的实现在抽象类中定义且不会改变。step2是一个抽象方法,需要子类(如ConcreteTemplateAConcreteTemplateB)根据具体需求实现。客户端代码通过创建子类的实例并调用templateMethod方法来执行算法。

源码中的作用

复用

模板模式有两大作用:复用和扩展。

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

Java InputStream

Java IO 类库中,有很多类的设计用到了模板模式,比如 InputStream、OutputStream、Reader、Writer。

把 InputStream 部分相关代码贴在了下面。在代码中,read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。不过这个方法也被命名为了 read(),只是参数跟模板方法不同。

public abstract class InputStream implements Closeable { //...省略其他代码... public int read(byte b[], int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); 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

在 Java AbstractList 类中,addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportedOperationException 异常。前提是,如果子类不重写是不能使用的。

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(); }

其在ArrayList中的实现如下:

public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); root.add(offset + index, element); updateSizeAndModCount(1); }

扩展

模板模式的第二大作用的是扩展。这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性,基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。

Java Servlet

对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC。利用它,只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果抛开这些高级框架来开发 Web 项目,必然会用到 Servlet。实际上,使用比较底层的 Servlet 来开发 Web 项目也不难。只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。具体的代码示例如下所示:

public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello World."); } }

除此之外,还需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等 Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL 和 Servlet 之间的映射关系。

<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.xzg.cd.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>

当在浏览器中输入网址(比如,127.0.0.1:8080/hello)的时候,Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的 Servlet(HelloServlet),然后执行它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用 doGet() 或 doPost() 方法,然后输出数据(“Hello world”)到网页。

HttpServlet 的 service() 函数长什么样子。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); // 子类实现的扩展点 doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 子类实现的扩展点 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { // 子类实现的扩展点 doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { // 子类实现的扩展点 doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { // 子类实现的扩展点 doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { // 子类实现的扩展点 doTrace(req,resp); } else { String errMsg = lStrings.getString("localhost:3306/ydlclass", "root", "123"); //2.创建statement类对象,用来执行SQL语句 stmt = conn.createStatement(); //3.ResultSet类,用来存放获取的结果集 String sql = "select * from user where id=" + id; ResultSet resultSet = stmt.executeQuery(sql); String eid = null, ename = null, price = null; while (resultSet.next()) { User user = new User(); user.setId(resultSet.getLong("id")); user.setName(resultSet.getString("name")); user.setTelephone(resultSet.getString("telephone")); return user; } } catch (ClassNotFoundException e) { // TODO: log... } catch (SQLException e) { // TODO: log... } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { // TODO: log... } if (stmt != null) try { stmt.close(); } catch (SQLException e) { // TODO: log... } } return null; } }

queryUser() 函数包含很多流程性质的代码,跟业务无关,比如,加载驱动、创建数据库连接、创建 statement、关闭连接、关闭 statement、处理异常。针对不同的 SQL 执行请求,这些流程性质的代码是相同的、可以复用的,不需要每次都重新敲一遍。

针对这个问题,Spring 提供了 JdbcTemplate,对 JDBC 进一步封装,来简化数据库编程。使用 JdbcTemplate 查询用户信息,只需要编写跟这个业务有关的代码,其中包括,查询用户的 SQL 语句、查询结果与 User 对象之间的映射关系。其他流程性质的代码都封装在了 JdbcTemplate 类中,不需要每次都重新编写。我用 JdbcTemplate 重写了上面的例子,代码简单了很多,如下所示:

public class JdbcTemplateDemo { @Resource 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 底层具体实现,JdbcTemplate 通过回调的机制,将不变的执行流程抽离出来,放到模板方法 execute() 中,将可变的部分设计成回调 StatementCallback,由用户来定制。query() 函数是对 execute() 函数的二次封装,让接口用起来更加方便。

@Override public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return query(sql, new RowMapperResultSetExtractor<T>(rowMapper)); } @Override public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback()); } @Override public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } T result = action.doInStatement(stmtToUse); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }

模板模式 VS 回调

回调的原理、实现和应用到此就都讲完了。从应用场景和代码实现两个角度,来对比一下模板模式和回调。

从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。

如何将Java模板模式应用于长尾词处理?

从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

回调相对于模板模式会更加灵活,主要体现在下面几点:

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

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

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

本文共计3552个文字,预计阅读时间需要15分钟。

如何将Java模板模式应用于长尾词处理?

模板模式是一种设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中。英文全称是 Template Method Design Pattern。这里的算法可以理解为广义上的业务逻辑,它不特指数据结构和具体算法实现。而算法框架则是指提供算法基本步骤和结构的模板,由子类具体实现细节。

模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。

这里的“算法”,可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

代码实现示例,如下所示。templateMethod() 函数定义为 final,是为了避免子类重写它。method1() 和 method2() 定义为 abstract,是为了强迫子类去实现。不过,这些都不是必须的,在实际的项目开发中,模板模式的代码实现比较灵活。

创建一个抽象类,定义算法的骨架:

public abstract class AbstractTemplate { // 模板方法,定义算法的骨架 public final void templateMethod() { step1(); step2(); step3(); } // 基本方法,定义算法中不会变化的步骤 private void step1() { System.out.println("Step 1: Prepare the ingredients."); } // 抽象方法,定义算法中需要子类实现的步骤 protected abstract void step2(); // 基本方法,定义算法中不会变化的步骤 private void step3() { System.out.println("Step 3: Serve the dish."); } }

创建具体的子类,实现抽象类中定义的抽象方法:

public class ConcreteTemplateA extends AbstractTemplate { @Override protected void step2() { System.out.println("Step 2 (A): Cook the dish using method A."); } } public class ConcreteTemplateB extends AbstractTemplate { @Override protected void step2() { System.out.println("Step 2 (B): Cook the dish using method B."); } }

在客户端代码中使用模板方法:

public class Main { public static void main(String[] args) { AbstractTemplate templateA = new ConcreteTemplateA(); AbstractTemplate templateB = new ConcreteTemplateB(); System.out.println("Using Template A:"); templateA.templateMethod(); System.out.println("\nUsing Template B:"); templateB.templateMethod(); } }

运行上面的程序,输出如下:

vbnetCopy code Using Template A: Step 1: Prepare the ingredients. Step 2 (A): Cook the dish using method A. Step 3: Serve the dish. Using Template B: Step 1: Prepare the ingredients. Step 2 (B): Cook the dish using method B. Step 3: Serve the dish.

AbstractTemplate是一个抽象类,它定义了一个名为templateMethod的模板方法。该方法包含三个步骤:step1step2step3。其中,step1step3是基本方法,它们的实现在抽象类中定义且不会改变。step2是一个抽象方法,需要子类(如ConcreteTemplateAConcreteTemplateB)根据具体需求实现。客户端代码通过创建子类的实例并调用templateMethod方法来执行算法。

源码中的作用

复用

模板模式有两大作用:复用和扩展。

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

Java InputStream

Java IO 类库中,有很多类的设计用到了模板模式,比如 InputStream、OutputStream、Reader、Writer。

把 InputStream 部分相关代码贴在了下面。在代码中,read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。不过这个方法也被命名为了 read(),只是参数跟模板方法不同。

public abstract class InputStream implements Closeable { //...省略其他代码... public int read(byte b[], int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); 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

在 Java AbstractList 类中,addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportedOperationException 异常。前提是,如果子类不重写是不能使用的。

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(); }

其在ArrayList中的实现如下:

public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); root.add(offset + index, element); updateSizeAndModCount(1); }

扩展

模板模式的第二大作用的是扩展。这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性,基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。

Java Servlet

对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC。利用它,只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果抛开这些高级框架来开发 Web 项目,必然会用到 Servlet。实际上,使用比较底层的 Servlet 来开发 Web 项目也不难。只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。具体的代码示例如下所示:

public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello World."); } }

除此之外,还需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等 Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL 和 Servlet 之间的映射关系。

<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.xzg.cd.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>

当在浏览器中输入网址(比如,127.0.0.1:8080/hello)的时候,Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的 Servlet(HelloServlet),然后执行它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用 doGet() 或 doPost() 方法,然后输出数据(“Hello world”)到网页。

HttpServlet 的 service() 函数长什么样子。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); // 子类实现的扩展点 doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 子类实现的扩展点 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { // 子类实现的扩展点 doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { // 子类实现的扩展点 doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { // 子类实现的扩展点 doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { // 子类实现的扩展点 doTrace(req,resp); } else { String errMsg = lStrings.getString("localhost:3306/ydlclass", "root", "123"); //2.创建statement类对象,用来执行SQL语句 stmt = conn.createStatement(); //3.ResultSet类,用来存放获取的结果集 String sql = "select * from user where id=" + id; ResultSet resultSet = stmt.executeQuery(sql); String eid = null, ename = null, price = null; while (resultSet.next()) { User user = new User(); user.setId(resultSet.getLong("id")); user.setName(resultSet.getString("name")); user.setTelephone(resultSet.getString("telephone")); return user; } } catch (ClassNotFoundException e) { // TODO: log... } catch (SQLException e) { // TODO: log... } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { // TODO: log... } if (stmt != null) try { stmt.close(); } catch (SQLException e) { // TODO: log... } } return null; } }

queryUser() 函数包含很多流程性质的代码,跟业务无关,比如,加载驱动、创建数据库连接、创建 statement、关闭连接、关闭 statement、处理异常。针对不同的 SQL 执行请求,这些流程性质的代码是相同的、可以复用的,不需要每次都重新敲一遍。

针对这个问题,Spring 提供了 JdbcTemplate,对 JDBC 进一步封装,来简化数据库编程。使用 JdbcTemplate 查询用户信息,只需要编写跟这个业务有关的代码,其中包括,查询用户的 SQL 语句、查询结果与 User 对象之间的映射关系。其他流程性质的代码都封装在了 JdbcTemplate 类中,不需要每次都重新编写。我用 JdbcTemplate 重写了上面的例子,代码简单了很多,如下所示:

public class JdbcTemplateDemo { @Resource 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 底层具体实现,JdbcTemplate 通过回调的机制,将不变的执行流程抽离出来,放到模板方法 execute() 中,将可变的部分设计成回调 StatementCallback,由用户来定制。query() 函数是对 execute() 函数的二次封装,让接口用起来更加方便。

@Override public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return query(sql, new RowMapperResultSetExtractor<T>(rowMapper)); } @Override public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback()); } @Override public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } T result = action.doInStatement(stmtToUse); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }

模板模式 VS 回调

回调的原理、实现和应用到此就都讲完了。从应用场景和代码实现两个角度,来对比一下模板模式和回调。

从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。

如何将Java模板模式应用于长尾词处理?

从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

回调相对于模板模式会更加灵活,主要体现在下面几点:

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

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

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