33 模版模式:命令模式-行为型模式

157 阅读7分钟

行为型模式之:

  • 观察者模式(Observer) ✅ 重要程度:⭐️⭐️⭐️⭐️⭐️

  • 迭代器模式(Iterator) ✅ 重要程度:⭐️⭐️⭐️⭐️⭐️

  • 策略模式(Strategy) ✅ 重要程度:⭐️⭐️⭐️⭐️

  • 模版模式(Template Method) ✅ 重要程度:⭐️⭐️⭐️⭐️

  • 职责链模式(Chain of Responsibility)
    重要程度:⭐️⭐️⭐️

  • 解释器模式(Interpreter)
    重要程度:⭐️

  • 中介者模式(Mediator)
    重要程度:⭐️⭐️

  • 备忘录模式(Memento)
    重要程度:⭐️⭐️

  • 状态模式(State)
    重要程度:⭐️⭐️⭐️

  • 命令方法模式(Command)
    重要程度:⭐️⭐️⭐️

  • 访问者模式(Visitor)
    重要程度:⭐️

1.什么是模版模式?

模板模式(Template Pattern)是一种行为设计模式,它定义了一个操作中的算法框架,将一些步骤的实现延迟到子类中。模板模式使得子类可以在不改变算法结构的情况下重新定义算法中的某些步骤。

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

一个典型的模板模式包含以下角色:

  1. 抽象类(Abstract Class) :定义了一个模板方法,其中包含算法的骨架以及一些基本的步骤,有时候可能会包含一些具体方法的实现。抽象类中的某些方法由子类来实现。
  2. 具体子类(Concrete Subclasses) :实现了抽象类中定义的抽象方法,完成了具体步骤的实现。

2.模版模式的使用

下面是一个简单的例子,说明了模板模式的应用:

假设我们有一个咖啡制作的流程,包括泡咖啡豆、加糖、加牛奶等步骤。这些步骤是固定的,但是具体的实现可以有所不同,因为有些人喜欢黑咖啡,有些人喜欢加糖加牛奶的拿铁。

// 抽象类定义了咖啡制作的模板方法
abstract class CoffeeTemplate {

    // 制作咖啡的模板方法
    public final void makeCoffee() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    // 烧水
    protected void boilWater() {
        System.out.println("Boiling water");
    }

    // 泡咖啡豆
    protected void brewCoffeeGrinds() {
        System.out.println("Brewing coffee grinds");
    }

    // 倒入杯子
    protected void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 加调料,抽象方法,由子类实现
    protected abstract void addCondiments();

    // 钩子方法,用于控制是否加调料,默认加调料
    protected boolean customerWantsCondiments() {
        return true;
    }
}

// 具体子类,实现加糖加牛奶的拿铁
class LatteCoffee extends CoffeeTemplate {

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

// 具体子类,实现黑咖啡
class BlackCoffee extends CoffeeTemplate {

    @Override
    protected void addCondiments() {
        // 黑咖啡不加调料
    }

    @Override
    protected boolean customerWantsCondiments() {
        return false;
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        CoffeeTemplate latte = new LatteCoffee();
        latte.makeCoffee();

        System.out.println();

        CoffeeTemplate blackCoffee = new BlackCoffee();
        blackCoffee.makeCoffee();
    }
}

在这个例子中,CoffeeTemplate 是抽象类,定义了制作咖啡的模板方法 makeCoffee(),并包含了一些基本的步骤,其中的某些步骤由具体的子类来实现。LatteCoffeeBlackCoffee 是具体的子类,分别实现了加糖加牛奶的拿铁和黑咖啡。通过模板模式,我们可以看到制作咖啡的整个流程被固定在了模板方法中,但是具体的实现可以由不同的子类来实现

3.常见的模版模式使用框架

模板方法模式是一种常见的设计模式,在许多开源框架和库中都有广泛的应用。以下是一些使用模板方法模式的常见开源框架和库:

  1. Spring Framework:Spring框架是Java开发中最流行的企业应用程序开发框架之一。Spring中的许多模块和组件都使用了模板方法模式,例如JdbcTemplate、HibernateTemplate等,这些模板类提供了一组模板方法,简化了与数据库交互和持久化操作。
  2. JUnit:JUnit是Java中最流行的单元测试框架之一。JUnit中的测试用例通常通过继承TestCase类并重写模板方法来实现,例如setUp()、tearDown()等方法。
  3. Servlet API:Java Servlet API中的HttpServlet类也使用了模板方法模式。开发者可以继承HttpServlet类并重写doGet()、doPost()等模板方法来处理HTTP请求。
  4. Android框架:在Android开发中,AsyncTask是一个常用的类,用于在后台线程执行异步任务并将结果返回到UI线程。AsyncTask中的doInBackground()、onPreExecute()、onPostExecute()等方法就是模板方法,开发者可以继承AsyncTask并重写这些方法来实现自定义的异步任务。
  5. Swing GUI库:Swing是Java中常用的GUI库之一,其中的JFrame、JDialog等组件也使用了模板方法模式。开发者可以继承这些组件并重写paintComponent()、paintChildren()等方法来实现自定义的GUI界面。
public abstract class HttpServlet extends GenericServlet {
    
    // doGet 方法处理 HTTP GET 请求
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 GET 请求的逻辑代码
    }

    // doPost 方法处理 HTTP POST 请求
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 POST 请求的逻辑代码
    }

    // 其他 HTTP 方法的处理逻辑
    // 这里省略了其他 HTTP 方法的处理方法,如doPut、doDelete等
    
    // service 方法是 HttpServlet 类中的模板方法
    // 它根据请求的方法调用相应的处理方法,如doGet、doPost等
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的 HTTP 方法
        String method = req.getMethod();
        
        // 根据请求的 HTTP 方法调用相应的处理方法
        if ("GET".equalsIgnoreCase(method)) {
            doGet(req, resp);
        } else if ("POST".equalsIgnoreCase(method)) {
            doPost(req, resp);
        } else {
            // 处理其他 HTTP 方法的逻辑代码
        }
    }

    // 其他方法
}

在上述代码中,HttpServlet 类定义了 doGetdoPost 等方法,用于处理 HTTP 请求中的不同方法(如 GET、POST)。然后,在 service 方法中,根据请求的 HTTP 方法调用相应的处理方法,这里的 service 方法就是模板方法。

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class MyServlet extends HttpServlet {

    // 重写 doGet 方法处理 HTTP GET 请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应内容类型
        resp.setContentType("text/html");

        // 实际的逻辑处理
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head><title>My Servlet</title></head>");
        out.println("<body>");
        out.println("<h1>Hello, World!</h1>");
        out.println("<p>This is a simple servlet example.</p>");
        out.println("</body></html>");
    }

    // 可选:重写 doPost 方法处理 HTTP POST 请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 实现 POST 请求的逻辑
    }
}

在上面的示例中,我们创建了一个名为 MyServlet 的类,继承自 HttpServlet。然后,我们重写了 doGet 方法和 doPost 方法,用于处理对应的 HTTP GET 和 POST 请求。

doGet 方法中,我们设置了响应的内容类型为 "text/html",然后向客户端输出了一个简单的 HTML 页面,显示 "Hello, World!"。在实际的应用中,可以根据需要编写更复杂的业务逻辑来处理请求。

开发者在编写自己的 Servlet 时,可以继承 HttpServlet 类,并重写 doGetdoPost 等方法来实现自定义的处理逻辑。这样,开发者无需关心请求的具体处理逻辑如何,只需专注于实现自己的业务逻辑即可。

4.模版模式的优缺点

优点:

  1. 提高代码复用性:模板模式将算法的通用部分抽象到父类中,使得子类只需专注于实现特定的步骤,从而提高了代码的复用性。
  2. 提高扩展性:由于模板模式将算法的结构固定在父类中,子类可以根据需要灵活地实现、重写或扩展特定的步骤,而不会影响到算法的整体结构。
  3. 提高代码的可维护性:模板模式使得算法的框架清晰可见,易于理解和维护。
  4. 封装了不变部分:模板模式将算法的不变部分封装在父类中,避免了重复编写相同的代码,降低了代码的维护成本。

缺点:

  1. 稳定性差:模板模式要求算法的结构是稳定的,如果算法的整体结构经常发生变化,可能会导致父类的修改,影响到子类的实现。
  2. 违反单一职责原则:有时候,模板模式可能会导致父类中的方法过于庞大,不利于维护和理解。
  3. 不易于拓展新功能:如果需要增加新的功能,可能需要修改父类的代码,这可能会影响到现有的子类。