模板方法模式的实现

1. 引言: 交易管理系统中的模板方法模式

之前做过一个交易管理系统,其中有一个核心模块是"交易流程管理",该模块需要处理不同类型的交易,比如期货交易、期权交易和股票交易。在构建交易管理系统的过程中,我们面临了一个核心挑战:如何高效地管理不同类型的交易流程?虽然这些交易类型在流程上存在差异,但它们的核心步骤却惊人地相似。如果为每一种交易类型都独立编写一套实现逻辑,不仅会增加工作量,还会导致代码冗余和维护难度的增加。

1.1 模板方法模式的引入

为了解决这一问题,我们采用了模板方法模式。这是一种行为设计模式,它允许我们将交易流程中的公共步骤抽象化,并在超类中定义这些步骤的执行顺序。同时,将那些因交易类型而异的特定步骤留给各个子类去实现。这样,我们不仅保持了交易流程的一致性,还为每种交易类型提供了定制化的灵活性。

1.2 本文目的

在本文中,我将深入探讨模板方法模式的基本概念、设计原则和实现步骤。通过实际案例分析,你将能够理解模板方法模式的工作原理,并学习如何在项目中有效地应用这一模式,以提升代码质量和开发效率

2. 模板方法模式的基本概念

模板方法模式是一种基于继承实现的设计模式,属于行为型模式。其主要思想是将定义的算法抽象成一组步骤,在抽象类中定义算法的骨架,而将具体的操作留给子类来实现。通俗地说,模板模式就是将某一行为制定一个框架,然后子类填充具体内容,使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。

2.1 模板方法模式的角色构成

  • 抽象类(Abstract Class:定义了一个模板方法,该方法包含了交易的处理步骤(算法骨架)。这个类还包含了一些基本方法,这些方法可以是抽象的(由子类实现),也可以是具体的(在抽象类中已经实现)。

  • 具体子类(Concrete Class) :继承自抽象类,并实现其中的抽象方法,以完成特定类型交易的具体处理步骤。

  • 钩子方法(Hook Method) (可选):在抽象类中提供默认实现,子类可以选择性地覆盖这些方法。

  • 客户端(Client)(可选): 使用模板方法来执行算法。

2.2 好处:模板方法模式的优势

模板方法模式在软件设计中具有多种好处,特别是在需要定义一个算法的骨架,同时允许子类提供某些步骤的具体实现时。

  • 代码复用:通过将算法的公共部分集中管理,减少了代码的重复。在交易管理系统中,不同交易类型的处理流程包括初始化、执行交易等公共步骤。通过模板方法模式,这些步骤只需在超类中实现一次,所有子类都可以复用这些代码。

  • 可扩展性:新增导出格式时,只需添加一个新的子类,而无需修改现有的算法框架。

  • 灵活性:子类可以根据自己的需求重写算法的特定步骤,而不影响其他部分。

  • 控制反转:模板方法模式将算法的控制权从子类转移到了超类,使得算法的流程更加清晰。如果交易的策略需要更新,只需在超类中修改相应的方法,所有子类都会自动继承这一更改。

2.3 坏处:模板方法模式的弊端

有优点就会有缺点,与其他设计模式一样,模板方法模式也有一些潜在的弊端。

  • 灵活性受限:模板方法模式必须通过定义一个固定的算法框架来实现代码复用,但这也意味着子类在实现时必须遵循这个框架。假设交易流程需要根据市场条件动态调整,例如在某些紧急情况下需要跳过某些步骤或增加额外的验证。模板方法模式的固定性可能会限制这种灵活性。

  • 增加复杂性:在模板方法模式中,算法的公共部分和可变部分被分离到不同的类中,这可能会使得理解和维护系统变得更加复杂。如果交易管理系统有多个交易类型,每个类型都有其特定的实现,开发人员可能需要花费额外的时间去理解整个模板方法的架构和各个子类的实现。

3. 模板方法模式遵循的设计原则

  1. 单一职责原则, 简称SRP。该原则强调每个类应该只有一个引起它变化的原因。在模板方法模式中,抽象类负责定义算法的骨架,具体子类负责实现特定的步骤,保证了每个类都有明确的职责。

  2. 开闭原则,简称OCP。该原则强调软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。模板方法模式通过在抽象类中定义算法的骨架,允许子类通过扩展(即实现抽象方法)来改变算法的行为,而不需要修改现有的代码。

  3. 里氏替换原则,简称LSP。该原则强调子类型必须能够替换掉它们的基类型。在模板方法模式中,具体子类可以替换抽象类,并且客户端代码可以透明地使用这些子类,而不会破坏程序的正确性。

  4. 依赖倒置原则,简称DIP。该原则强调高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。模板方法模式中,高层模块(如客户端代码)依赖于抽象类,而不是具体的实现类,从而实现了依赖的倒置。

  5. 接口隔离原则,简称ISP。该原则强调客户端不应该被迫依赖它们不使用的方法。在模板方法模式中,抽象类定义了模板方法和基本方法,但具体子类只需要实现它们需要的方法,不需要实现不相关的方法。

  6. 迪米特法则,简称LoD。该原则强调一个对象应该对其他对象有最少的了解。模板方法模式中,客户端只需要知道抽象类和模板方法,而不需要了解具体子类的实现细节,从而减少了对象之间的耦合。

当然,这都是基于业务逻辑相对没那么复杂,且处理的流程不需要频繁可变的情况。实际项目中,可能有时候没法过于理想化的遵循所有的设计原则。以下是一些可能的情况:

  1. 单一职责原则(SRP) :虽然模板方法模式将算法的骨架和具体实现分离,但如果抽象类中定义了过多的基本方法,或者子类需要实现过多的具体步骤,可能会导致抽象类或子类的职责不够单一。

  2. 开闭原则(OCP) :虽然模板方法模式允许通过扩展子类来改变算法的行为,但如果算法的骨架需要频繁修改,或者需要频繁增加新的基本方法,可能会导致抽象类需要不断修改,从而违反开闭原则。

  3. 里氏替换原则(LSP) :如果子类的实现与抽象类的预期行为不一致,或者子类错误地重写了基本方法,可能会导致子类无法正确替换抽象类,从而违反里氏替换原则。

  4. 依赖倒置原则(DIP) :虽然模板方法模式鼓励依赖于抽象类,但如果抽象类的设计不够稳定,或者抽象类与具体子类之间的耦合度过高,可能会导致高层模块仍然依赖于低层模块的细节,从而违反依赖倒置原则。

  5. 接口隔离原则(ISP) :如果抽象类中定义了过多的基本方法,或者某些基本方法对于某些子类来说并不适用,可能会导致子类被迫实现不必要的方法,从而违反接口隔离原则。

  6. 迪米特法则(LoD) :虽然模板方法模式减少了客户端对具体子类的了解,但如果抽象类与具体子类之间的交互过于复杂,或者抽象类暴露了过多的实现细节,可能会导致对象之间的耦合度过高,从而违反迪米特法则。

4. 模板方法模式的实现流程

下面是一个交易管理系统的伪代码例子,展示如何使用模板方法模式来处理不同类型的交易(如期货交易、期权交易和股票交易)的处理流程。流程大约分为3步。

  1. 定义抽象类 :创建一个抽象类 TransactionProcessor,其中包含一个模板方法 processTransaction(),以及一些基本方法,如 convertParameters()validateParameters()additionalProcessing()
java 复制代码
// 抽象类
abstract class TransactionProcessor {

    // 模板方法
    public final void processTransaction(Object obj) {
        Object convertedParams = convertParameters(obj);
        validateParameters(convertedParams);
        additionalProcessing(convertedParams);
    }

    // 基本方法
    protected abstract Object convertParameters(Object obj);
    protected abstract void validateParameters(Object convertedParams);

    // 钩子方法(可选)
    protected void additionalProcessing(Object convertedParams) {
        // 默认实现为空,子类可以选择性地覆盖
    }
}
  1. 实现具体子类 :创建具体子类 FuturesTransactionProcessorOptionsTransactionProcessorStockTransactionProcessor,它们分别实现期货交易、期权交易和股票交易的处理流程。
java 复制代码
// 具体子类:期货交易
class FuturesTransactionProcessor extends TransactionProcessor 

    @Override
    protected Object convertParameters(Object obj) {
        // 这里进行具体的参数转换逻辑
        // ...
        
        // 返回转换后的参数
        return new Object(); 
    }

    @Override
    protected void validateParameters(Object convertedParams) {
        // 这里进行具体的参数校验逻辑
    }

    @Override
    protected void additionalProcessing(Object convertedParams) {
        System.out.println("Performing additional processing for futures transaction...");
        // 这里进行具体的额外处理逻辑
        // ...
    }
}
java 复制代码
// 具体子类:期权交易
class OptionsTransactionProcessor extends TransactionProcessor {
    @Override
    protected Object convertParameters(Object obj) {
        // 这里进行具体的参数转换逻辑
        // ...
        
        // 返回转换后的参数
        return new Object(); 
    }

    @Override
    protected void validateParameters(Object convertedParams) {
        // 这里进行具体的参数校验逻辑
        // ...
    }

    @Override
    protected void additionalProcessing(Object convertedParams) {
        // 这里进行具体的额外处理逻辑
        // ...
    }
}
java 复制代码
// 具体子类:股票交易
class StockTransactionProcessor extends TransactionProcessor {

    @Override
    protected Object convertParameters(Object obj) {
        // 这里进行具体的参数转换逻辑
        // ...
        
        // 返回转换后的参数
        return new Object(); 
    }

    @Override
    protected void validateParameters(Object convertedParams) {
        // 这里进行具体的参数校验逻辑
        // ...
    }

    // 股票交易不需要额外的处理逻辑,因此不覆盖 additionalProcessing 方法
}
  1. 使用模板方法:在客户端代码中使用模板方法来执行交易处理流程。
java 复制代码
// 客户端代码
public class TradingSystem {

    public static void main(String[] args) {
        
        TransactionProcessor futuresProcessor = new FuturesTransactionProcessor();
        futuresProcessor.processTransaction(new Object());

        TransactionProcessor optionsProcessor = new OptionsTransactionProcessor();
        optionsProcessor.processTransaction(new Object());

        TransactionProcessor stockProcessor = new StockTransactionProcessor();
        stockProcessor.processTransaction(new Object());
    }
}

5. 模板方法模式在实际框架中的使用

5.1 HttpServlet 中的模板方法模式

Java Servlet API 中的 HttpServlet 类使用了模板方法模式。HttpServlet 类定义了处理 HTTP 请求的模板方法 service,而具体的请求处理逻辑(如 doGet、doPost 等)则由子类来实现。

java 复制代码
public abstract class HttpServlet extends GenericServlet {

private static final ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");

   protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
   
        String method = req.getMethod();
        long lastModified; 
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_get_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_post_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    // 其他方法...
}

5.2 JdbcTemplate 中的模板方法模式

Spring Framework 中的 JdbcTemplate 类也使用了模板方法模式。JdbcTemplate 提供了执行 SQL 查询和更新的模板方法,而具体的 SQL 语句和参数则由回调函数来提供。

java 复制代码
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {

    @Nullable
    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 (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing SQL query [" + sql + "]");
        }

        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            QueryStatementCallback() {
            }

            @Nullable
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;

                Object var3;
                try {
                    rs = stmt.executeQuery(sql);
                    var3 = rse.extractData(rs);
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }

                return var3;
            }

            public String getSql() {
                return sql;
            }
        }

        return this.execute((StatementCallback)(new QueryStatementCallback()));
    }

    public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
        // 创建并执行 PreparedStatement
        // 处理 ResultSet
        // 返回结果
    }

    // 其他方法...
}

5.3 RestTemplate 中的模板方法模式

RestTemplate 提供了一系列的 exchange 和 execute 方法,这些方法定义了 HTTP 请求的执行流程,而具体的请求细节(如请求方法、URL、请求头、请求体等)则由子类或回调函数来提供。

java 复制代码
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
	
	@Nullable
    public <T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        return this.doExecute(url, method, requestCallback, responseExtractor);
    }

	@Nullable
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        Assert.notNull(url, "URI is required");
        Assert.notNull(method, "HttpMethod is required");
        // 1. 创建请求
        ClientHttpResponse response = null;

        Object var14;
        try {
            ClientHttpRequest request = this.createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }

            response = request.execute();
            this.handleResponse(url, method, response);
            var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
        } catch (IOException var12) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
            throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
        } finally {
            if (response != null) {
                response.close();
            }

        }

        return var14;
    }
}
相关推荐
惜.己3 天前
设计模式之模板方法模式
设计模式·模板方法模式
程序员与背包客_CoderZ4 天前
C++设计模式——Template Method模板方法模式
linux·开发语言·c++·设计模式·模板方法模式
記億揺晃着的那天5 天前
23种设计模式之模版方法模式
java·设计模式·模板方法模式
且随疾风前行.9 天前
技术成神之路:设计模式(十二)模板方法模式
java·设计模式·模板方法模式
Fuliy9611 天前
游戏开发设计模式之模板方法模式
java·游戏·设计模式·游戏开发·模板方法模式
勇敢滴勇14 天前
ACM模式下算法题输入输出攻略【C++】
开发语言·c++·算法·模板方法模式
谢尔登15 天前
【设计模式】模板方法模式和迭代器模式
设计模式·迭代器模式·模板方法模式
潘多编程17 天前
Spring Boot实战:使用模板方法模式优化数据处理流程
spring boot·后端·模板方法模式
真的是这样的吗_22 天前
设计模式-模板方法模式
java·设计模式·模板方法模式
不是仙人的闲人1 个月前
C++ 设计模式——模板方法模式
c++·设计模式·模板方法模式