模板方法(学习笔记)

  1. 意图

  定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

  2. 动机

   假设正在开发一款分析公司文档的数据挖掘程序。用户需要向程序输入各种格式(PDF、DOC或CSV)的文档,程序则会从这些文档中抽取出有意义的数据,并以统一的格式将其返回给用户。一段时间后,你发现这三个类包含了许多相似的代码。尽管这些类处理不同数据格式的代码完全不同,但是数据处理和分析的代码却完全一样。怎样能在保持算法结构完整的情况下去除重复代码呢?另外,还有一个与使用这些类的客户端代码相关的问题:客户端代码中包含许多条件语句,以根据不同的处理对象类型选择合适的处理过程。如果所有处理数据的类都拥有相同的接口或基类 那么你就可以去除客户端代码中的条件语句 转而使用多态机制来在处理对象上调用函数

 

  模板方法模式建议将算法分解为一系列步骤,然后将这些步骤改写为方法,最后在 “模板方法” 中依次调用这些方法。步骤可以是抽象的,也可以有一些默认的实现。为了能够使用算法,客户端需要自行提供子类并实现所有的抽象步骤。如有必要还需重写一些步骤(但这一步中不包括模板方法自身)。有两种类型的步骤

  • 抽象步骤必须由各个子类来实现
  • 可选步骤已有一些默认实现 但仍可在需要时进行重写

  还有另一种名为钩子的步骤钩子是内容为空的可选步骤即使不重写钩子模板方法也能工作钩子通常放置在算法重要步骤的前后为子类提供额外的算法扩展点 

  3. 适用性

  • 一次性的实现一个算法的不变部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为应该被提取出来并集中到一个公共父类中以避免代码重复。
  • 控制子类扩展。模板方法只在特定点调用钩子操作,这样只允许在这些点进行扩展

  4. 结构

   

  5. 效果

  模板方法是一种代码复用的基本技术。它们在类库中尤为重要,提取了类库中的公共行为。模板方法导致了一种反向的控制结构,这种结构有时被称为好莱坞法则,即一个父类调用一个子类的操作,而不是相反

  模板方法调用下列类型的操作:

  1. 具体操作(ConcreteClass或对客户类的操作)

  2. 具体的AbstractClass的操作

  3. 原语操作(AbstractClass中定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤)

  4. 工厂方法

  5. 钩子操作,它提供了缺省行为,子类可以在必要时进行扩展。钩子操作在通常情况下是空操作  

  6. 代码实现  

  本例中模版方法模式定义了一个可与社交网络协作的算法与特定社交网络相匹配的子类将根据社交网络所提供的API来实现这些步骤

  networks/Network.java: 基础社交网络类

package template_method.networks;

/**
 * @author GaoMing
 * @date 2021/7/25 - 21:47
 * Base class of social network.
 */
public abstract class Network {
    String userName;
    String password;

    Network() {}

    /**
     * Publish the data to whatever network.
     */
    public boolean post(String message) {
        // Authenticate before posting. Every network uses a different
        // authentication method.
        if (logIn(this.userName, this.password)) {
            // Send the post data.
            boolean result =  sendData(message.getBytes());
            logOut();
            return result;
        }
        return false;
    }

    abstract boolean logIn(String userName, String password);
    abstract boolean sendData(byte[] data);
    abstract void logOut();
}

  networks/Facebook.java: 具体社交网络

package template_method.networks;

/**
 * @author GaoMing
 * @date 2021/7/25 - 21:47
 */
public class Facebook extends Network {
    public Facebook(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Facebook");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Facebook");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Facebook");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

  networks/Twitter.java: 另一个社交网络

package template_method.networks;

/**
 * @author GaoMing
 * @date 2021/7/25 - 21:47
 */
public class Twitter extends Network{
    public Twitter(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Twitter");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Twitter");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Twitter");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

  Demo.java: 客户端代码

package template_method;

import template_method.networks.Network;
import template_method.networks.Twitter;
import template_method.networks.Facebook;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author GaoMing
 * @date 2021/7/25 - 21:47
 */
public class Demo {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        Network network = null;
        System.out.print("Input user name: ");
        String userName = reader.readLine();
        System.out.print("Input password: ");
        String password = reader.readLine();

        // Enter the message.
        System.out.print("Input message: ");
        String message = reader.readLine();

        System.out.println("\nChoose social network for posting message.\n" +
                "1 - Facebook\n" +
                "2 - Twitter");
        int choice = Integer.parseInt(reader.readLine());

        // Create proper network object and send the message.
        if (choice == 1) {
            network = new Facebook(userName, password);
        } else if (choice == 2) {
            network = new Twitter(userName, password);
        }
        network.post(message);
    }
}

  运行结果    

Input user name: Jhonatan
Input password: qswe
Input message: Hello, World!

Choose social network for posting message.
1 - Facebook
2 - Twitter
2

Checking user's parameters
Name: Jhonatan
Password: ****
..........

LogIn success on Twitter
Message: 'Hello, World!' was posted on Twitter
User: 'Jhonatan' was logged out from Twitter

 

  7. 与其他模式的关系

  • 工厂方法模式是模板方法模式的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤
  • 模板方法基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法。策略模式基于组合机制:可以通过对相应行为提供不同的策略来改变对象的部分行为。模板方法在类层次上运作,因此它是静态的。策略在对象层次上运作,因此允许在运行时切换行为

  8. 已知应用  

  使用示例:模版方法模式在Java框架中很常见。开发者通常使用它来向框架用户提供通过继承实现的、对标准功能进行扩展的简单方式
  这里是一些核心 Java 程序库中模版方法的示例:
  java.io.InputStream、java.io.OutputStream、java.io.Reader和java.io.Writer的所有非抽象方法
  java.util.AbstractList、java.util.AbstractSet和java.util.AbstractMap的所有非抽象方法
  javax.servlet.http.HttpServlet,所有默认发送HTTP 405 “方法不允许” 错误响应的do­XXX()方法。你可随时对其进行重写

  识别方法 模版方法可以通过行为方法来识别 该方法已有一个在基类中定义的 “默认 行为

posted @ 2021-07-28 15:08  慕仙白  阅读(364)  评论(0编辑  收藏  举报