🐝

【TS】今さら聞けないテンプレートメソッドパターン

2020/11/27に公開

はじめに

今回はテンプレートメソッドパターン(TemplateMethod Pattern)について解説します。
処理の枠組みを定めて、具体的な実装をサブクラスで行わせることで取り回しが効きやすくなるデザインパターンです。

今回はテンプレートメソッドパターンとは?

TECHSCOREさんの解説があります。

以下、引用です。

第3章では TemplateMethod パターンを紹介します。 template とは、文字の形に穴があいている薄いプラスチックの板のことです。その穴をペンでなぞると、手書きでも整った文字を書くことができます。しかし、マジックで書くのか、鉛筆で書くのか、それとも墨汁と筆を使ってみるのかということを考えてみると、実際にどのような文字になるのかはわかりません。template は、ただその形を決定する枠組みを提供するだけです。

TemplateMethod パターンは、テンプレートの機能を持つパターンです。スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を実装します。スーパークラスでは、アルゴリズムの流れの中で利用される抽象的なメソッドと、この抽象的なメソッドを利用して、処理のアルゴリズムを定義する templateMethod メソッドを定義します。

ポイントは 「スーパークラスで処理の枠組みを定め」 という箇所です。
スーパークラスが枠組み"だけ"を定めて、実際に何をするかはサブクラスの実装によります。

閑話休題

TypescriptJavascriptで見かける 「テンプレートリテラル」 を知っていると理解しやすいかもしれません。

const zenn: string = 'Zenn太郎';
console.log(`こんにちは!${zenn}さん`); // --> こんにちはZenn太郎さん

テンプレートリテラルも、雛形となるテンプレート(コード中でいう「こんにちは・・・さん」の部分)が決まっていて、そこにzennという具体的な値を当てはめています。
テンプレートリテラルについては下記の記事で解説しているので、よろしければそちらをご覧になってください。

例題

メールを送信するクラスを定義したいと思います。
メールは以下のクラスで示されるように、「件名」と「本文」と「フッター」から構成されます。

class Mail {
    title: string = '';
    body: string = '';
    footer: string = '';
}

このメールを送信するMailSenderというクラスを考えた時、
その件名や本文、フッターの内容については作成者によってまちまちだと思います。

例えば「英語を使ってメールを書く」場合や「かしこまった内容のメールを書く」場合、あるいはその逆で「くだけた内容のメールを書く」場合などです。
上記のポイントは 「件名、本文、フッターを書いてから送信する」 という処理だけは共通しているという点です。
従ってこの場合、テンプレートメソッドパターンで記載することができます。

MailSendersend()という処理の枠組みを定義し、メールの内容を生成する関数はここでは定義しないものとします。

abstract class MailSender {
    /** 件名を書く */
    abstract writeTitle(mail: Mail):void;
    /** 本文を書く */
    abstract writeBody(mail: Mail):void;
    /** フッターを書く */
    abstract writeFooter(mail: Mail):void;
    
    /** 送信処理 */
    send = () => {
        let mail = new Mail();
        this.writeTitle(mail);
        this.writeBody(mail);
        this.writeFooter(mail);

        // コンソール出力(≒送信したものとする)
        console.log(mail);
    }    
}

send()ではwriteTitleにはじまり、諸々の内容を記載した後に送信するメールをコンソール出力しています。
このMailSenderを実装することで、「メールを送信する」という枠組みを満たしたサブクラスを生成できます。

class JapaneseMailSender extends MailSender {
    writeTitle = (mail: Mail) => mail.title = '件名';
    writeBody = (mail: Mail) => mail.body = '本文';
    writeFooter = (mail: Mail) => mail.footer = 'フッター';
}

class EnglishMailSender extends MailSender {
    writeTitle = (mail: Mail) => mail.title = 'Title';
    writeBody = (mail: Mail) => mail.body = 'Body';
    writeFooter = (mail: Mail) => mail.footer = 'Footer';
}

const jp: JapaneseMailSender = new JapaneseMailSender();
jp.send(); // --> { title: '件名', body: '本文', footer: 'フッター' }
const en: EnglishMailSender = new EnglishMailSender();
en.send(); // --> { title: 'Title', body: 'Body', footer: 'Footer' }

まとめ

今回はTypescriptでデザインパターンの一つである TemplateMethod Pattern について紹介しました。
特徴として「処理の枠組みを定義」することで、サブクラスでその枠組みに従った実装を行うことができるようになります。
処理の大枠はそのままに、中身だけを自由に取り回せるようになるのがメリットです。

GoFのデザインパターンの中でも比較的取り入れやすい内容だと思うので、是非試されてみてはいかがでしょうか。

Discussion