Day 27: Behavioral patterns - Template Method

目的

亲代物件负责架构,实作细节则交给继承的子代物件负责。

说明

试想一个情境,物件内某个方法的实作内容有各种可能,如果没有思考,可能会依照实作内容的不同,建立多个物件。

class Operation1 {
  operation() {
    // 相同处,都做点什麽
    // 差异处 AAA
  }
}

class Operation2 {
  operation() {
    // 相同处,都做点什麽
    // 差异处 BBB
  }
}

随着物件数量增加,大量重复的程序码会不断出现。此时,该思考的是,如何将重复的部分抽出,并且不影响原有功能。刚好,每个物件的差异都在实作的细节,所以可以建立一个亲代物件,除了将共同的程序码封装之外,建立一个供子代实作但亲代只负责开规格的方法。

class OperationPrototype {
  operation() {
    // 相同处,都做点什麽
  }

  operationDetails() { }
}

class Operation1 extends OperationPrototype {
  /** @override */
  operationDetails() { }() {
    // 差异处 AAA
  }
}

class Operation2 extends OperationPrototype {
  /** @override */
  operationDetails() { }() {
    // 差异处 BBB
  }
}

如此一来,子代只要负责实作的内容,整体的框架仍然由亲代制定,子代没有能力做更动。

作法是:

  1. 观察多个物件,是不是差异处都在实作的部分。
  2. 将共同的程序码建立成亲代虚拟层,并且开规格,供子代实作细节。
  3. 建立子代,只要实作亲代内,需要子代实作的方法。
  4. 使用时,建立子代但使用亲代的共同程序码内的方法。

以下范例以「简易打扫家庭」为核心制作。

UML 图

Template Method Pattern UML Diagram

使用 Java 实作

建立亲代虚拟层物件:Housekeeping

public abstract class Housekeeping {
    protected String type;

    Housekeeping(String type) {
        this.type = type;
    }

    public String washClothes() {
        return washDetails();
    }

    protected abstract String washDetails();

    public String cleanUp() {
        return cleanUpDetails();
    }

    protected abstract String cleanUpDetails();
}

建立子代物件:SimpleHousekeepingAppliancesHousekeeping(Template 物件)

public class SimpleHousekeeping extends Housekeeping {
    public SimpleHousekeeping() {
        super("简单的打扫方式");
    }

    @Override
    protected String washDetails() {
        System.out.println("手洗衣服");

        for (int i = 0; i < 10; i++) {
            System.out.println("手洗中" + ".".repeat(i));
        }

        return "一堆乾净的衣服,花了两小时";
    }

    @Override
    protected String cleanUpDetails() {
        System.out.println("用扫把打扫");

        for (int i = 0; i < 8; i++) {
            System.out.println("打扫中" + ".".repeat(i));
        }

        return "乾净的房间,腰酸背痛";
    }

}

public class AppliancesHousekeeping extends Housekeeping {
    public AppliancesHousekeeping() {
        super("家电帮忙下的打扫方式");
    }

    @Override
    protected String washDetails() {
        System.out.println("洗衣机洗衣服");

        for (int i = 0; i < 5; i++) {
            System.out.println("嘟".repeat(i));
        }

        return "一堆乾净的衣服,花了一小时";
    }

    @Override
    protected String cleanUpDetails() {
        System.out.println("用吸尘器打扫");

        for (int i = 0; i < 3; i++) {
            System.out.println("呜".repeat(i));
        }

        return "乾净的房间,轻松完成";
    }

}

测试,模拟使用不同器具打扫家庭:TicketMachineStrategySample

public class HousekeepingTemplateSample {
    public static void main(String[] args) {
        Housekeeping housekeeping = null;
        String washClothesResult = null;
        String cleanUpResult = null;

        System.out.println("没什麽闲钱,整理家务简单即可");
        housekeeping = new SimpleHousekeeping();
        washClothesResult = housekeeping.washClothes();
        cleanUpResult = housekeeping.cleanUp();
        System.out.println("清理结果: " + washClothesResult + ";" + cleanUpResult);

        System.out.println("\n有点钱了,买些家电帮忙整理家务");
        housekeeping = new AppliancesHousekeeping();
        washClothesResult = housekeeping.washClothes();
        cleanUpResult = housekeeping.cleanUp();
        System.out.println("\n清理结果: " + washClothesResult + ";" + cleanUpResult);
    }
}

使用 JavaScript 实作

建立亲代虚拟层物件:Housekeeping

/** @abstract */
class Housekeeping {
  /** @param {string} type */
  constructor(type) {
    this.type = type;
  }

  washClothes() {
    return this.washDetails();
  }

  /** @abstract */
  washDetails() { return ""; }

  cleanUp() {
    return this.cleanUpDetails();
  }

  /** @abstract */
  cleanUpDetails() { return ""; }
}

建立子代物件:SimpleHousekeepingAppliancesHousekeeping(Template 物件)

class SimpleHousekeeping extends Housekeeping {
  constructor() {
    super("简单的打扫方式");
  }

  /** @override */
  washDetails() {
    console.log("手洗衣服");

    for (let i = 0; i < 10; i++) {
      console.log("手洗中" + ".".repeat(i));
    }

    return "一堆乾净的衣服,花了两小时";
  }

  /** @override */
  cleanUpDetails() {
    console.log("用扫把打扫");

    for (let i = 0; i < 8; i++) {
      console.log("打扫中" + ".".repeat(i));
    }

    return "乾净的房间,腰酸背痛";
  }
}

class AppliancesHousekeeping extends Housekeeping {
  constructor() {
    super("家电帮忙下的打扫方式");
  }

  /** @override */
  washDetails() {
    console.log("洗衣机洗衣服");

    for (let i = 0; i < 5; i++) {
      console.log("嘟".repeat(i));
    }

    return "一堆乾净的衣服,花了一小时";
  }

  /** @override */
  cleanUpDetails() {
    console.log("用吸尘器打扫");

    for (let i = 0; i < 3; i++) {
      console.log("呜".repeat(i));
    }

    return "乾净的房间,轻松完成";
  }
}

测试,模拟使用不同器具打扫家庭:TicketMachineStrategySample

const housekeepingTemplateSample = () => {
  let housekeeping = null;
  let washClothesResult = null;
  let cleanUpResult = null;

  console.log("没什麽闲钱,整理家务简单即可");
  housekeeping = new SimpleHousekeeping();
  washClothesResult = housekeeping.washClothes();
  cleanUpResult = housekeeping.cleanUp();
  console.log("清理结果: " + washClothesResult + ";" + cleanUpResult);

  console.log("\n有点钱了,买些家电帮忙整理家务");
  housekeeping = new AppliancesHousekeeping();
  washClothesResult = housekeeping.washClothes();
  cleanUpResult = housekeeping.cleanUp();
  console.log("\n清理结果: " + washClothesResult + ";" + cleanUpResult);
};

housekeepingTemplateSample();

总结

Template Method 模式有趣在於,如果常常使用 OOP 原则 - 继承(Inheritance)的话,那阅读这个模式的内容时会恍然大悟,并且有感而发:「原来平常开发的习惯,即使再怎麽简单,也是一种模式?!」

那你早就默默在用了啊

想想也是如此,没有实际接触、了解前,不用把不知道的事情想成是伟大、繁琐、有难度的、不易了解等等,徒然增加自身在学习上的障碍物。

明天将介绍 Behavioural patterns 的第十一个也是该类别最後一个模式:Visitor 模式。


<<:  Day29:【技术篇】初探打包工具的存在?

>>:  【把玩Azure DevOps】Day30 2021铁人赛结尾感言

Progressive Web App 取得 Geolocation API 地理资讯 (28)

什麽是 Geolocation API? 透过 Geolocation API 可以让 Progre...

Day 16 弹性使用网路安全-WAF

网际网路的兴盛离不开Http、Https两种协议的功劳,但在网路世界中,DDOS、CC Attac...

Day17 - 【概念篇】OAuth flows: Client Credentials

本系列文之後也会置於个人网站 +---------+ +---------------+ | | ...

简报版-第九章-认识物联网安全~从各式联网设备的风险看起

其实原本最初规画想要做Index方式的纪录,然後多增加一些没写到的面向 不过,总是计画赶不上变化 ...

Day4 跟着官方文件学习Laravel-CSRF保护

举例: 想像你的产品有个/user/email route允许post request去修改已经认证...