iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0

目的

親代物件負責架構,實作細節則交給繼承的子代物件負責。

說明

試想一個情境,物件內某個方法的實作內容有各種可能,如果沒有思考,可能會依照實作內容的不同,建立多個物件。

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 模式。


上一篇
Day 26: Behavioral patterns - Strategy
下一篇
Day 28: Behavioral patterns - Visitor
系列文
也該是時候學學 Design Pattern 了31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言