3753817_s

本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にテンプレートメソッド(Template Method)をC++で実装していきます。




テンプレートメソッドとは


テンプレートメソッドとは、テンプレートの機能を持つメソッドが親クラスで定義されており、そのメソッドの中の中小メソッドを子クラスで実装する方法です。

例えばRPGの戦闘をテンプレートメソッドを使って実装するとします。

ここでは戦闘は以下の流れで行うとします。
  1. 移動
  2. 攻撃
移動と攻撃の方法はRPGでいう戦士や魔法使いの役職で変わるとします。

そこで、親クラスで先ほどの戦闘の流れを呼ぶためのテンプレートメソッドと移動と攻撃の抽象メソッドを定義します。

そして、子クラスとして戦士クラスや魔法使いクラスを用意して、移動や攻撃の具体的な実装を行っていきます。


このようにスーパークラスで処理の枠組みを定め、サブクラスでその具体的な内容を定めるデザインパターンをテンプレートメソッドパターン(Template Method)と呼びます。



テンプレートメソッドの登場人物


テンプレートメソッドパターンは以下のようなクラス構成になっています。


テンプレートメソッド



AbstractClass(抽象クラス)


AbstractClass役はテンプレートメソッドを実装します。

また、テンプレートメソッドの他にテンプレートメソッドで使用している抽象メソッドも宣言します。

これらの抽象メソッドの具体的な実装は子クラスであるConcreateClassによって行われます。


ConcreateClass(具象クラス)


AbstractClassで宣言された抽象メソッドを具体的に実装する役です。

ここで実装したメソッドがAbstractClassのテンプレートメソッドにより、呼び出されます。





C++による実装


今回は、文字列や文字を5回表示するといったプログラムをテンプレートメソッドパターンを使用して実装していきます。

クラス構成は以下の通りです。


テンプレートメソッド_sample


AbstractDisplayクラス


今回のサンプルプログラムではAbstractDisplayクラスがテンプレートクラスにあたります。

具体的な実装は、displayメソッドだけ行います。

displayメソッドは、「open → printを5回呼ぶ → close」という流れのメソッドになります。

■ヘッダファイル

#ifndef ABSTRACT_DISPLAY_H
#define ABSTRACT_DISPLAY_H

class AbstractDisplay{
public:
    virtual ~AbstractDisplay(){};
    virtual void open()=0;
    virtual void print()=0;
    virtual void close()=0;
    virtual void display() final;
};
#endif // ABSTRACT_DISPLAY_H


■ソースファイル

#include "AbstractDisplay.h"

void AbstractDisplay::display(){
    open();

    for(int i=0i<5; ++i){
        print();
    }

    close();
}



CharDisplayクラス


CharDisplayクラスの各メソッドは以下のような処理を行います。

メソッド名処理
open文字列"<<"を表示する
printコンストラクタで与えられていた文字を表示する
close文字列">>"を表示する


コンストラクタで'H'という文字列が与えられた場合、AbstractDisplayクラスで定義したdisplayメソッドを呼び出すと、

<<HHHHH>>

のように出力されます。


■ヘッダファイル

#ifndef CHAR_DISPLAY_H
#define CHAR_DISPLAY_H

#include "AbstractDisplay.h"

class CharDisplay : public AbstractDisplay{
public:
    CharDisplay(char ch);
    virtual ~CharDisplay();
    void open() override;
    void print() override;
    void close() override;

private:
    char m_ch;
};
#endif // CHAR_DISPLAY_H


■ソースファイル

#include <iostream>
#include "CharDisplay.h"

CharDisplay::CharDisplay(char ch) : m_ch(ch){}

CharDisplay::~CharDisplay(){};

void CharDisplay::open(){
    std::cout << "<<";
}

void CharDisplay::print(){
    std::cout << m_ch;
}
 
void CharDisplay::close(){
    std::cout << ">>" << std::endl;



StringDisplayクラス


StringDisplayクラスの各メソッドは以下のような処理を行います。

メソッド名処理
open文字列"+-----+"を表示する
printコンストラクタで与えられていた文字列を"|"と"|"ではさんで表示する
close文字列"+-----+"を表示する


コンストラクタで'Hello'という文字列が与えられた場合、AbstractDisplayクラスで定義したdisplayメソッドを呼び出すと、

+-----+
|Hello|
|Hello|
|Hello|
|Hello|
|Hello|
+-----+

のように出力されます。


■ヘッダファイル

#ifndef STRING_DISPLAY_H
#define STRING_DISPLAY_H

#include <iostream>
#include "AbstractDisplay.h"

class StringDisplay : public AbstractDisplay{
public:    
    StringDisplay(std::string string);
    virtual ~StringDisplay();
    void open() override;
    void print() override;
    void close() override;

private:
    std::string m_string;
};
#endif // STRING_DISPLAY_H


■ソースファイル

#include <iostream>
#include "StringDisplay.h"

StringDisplay::StringDisplay(std::string string) : m_string(string){}

StringDisplay::~StringDisplay(){};

void StringDisplay::open(){
    std::cout << "+";
    for(int i=0im_string.length(); ++i){
        std::cout << "-";
    }
    std::cout << "+" << std::endl;
}

void StringDisplay::print(){
    std::cout << "|" << m_string << "|" << std::endl;
}

void StringDisplay::close(){
    std::cout << "+";
    for(int i=0im_string.length(); ++i){
        std::cout << "-";
    }
    std::cout << "+" << std::endl;



main関数


今まで作成してきたクラスを実際に使っていきます。


■ソースファイル

#include "AbstractDisplay.h"
#include "CharDisplay.h"
#include "StringDisplay.h"

int main(void){
    AbstractDisplay *d1 = new CharDisplay('H');
    AbstractDisplay *d2 = new StringDisplay("Hello, world.");

    d1->display();
    d2->display();

    delete d1;
    delete d2;

    return 0;
}


■出力結果

<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+





まとめ


テンプレートメソッドパターンでは、テンプレートメソッドでアルゴリズムが定義されているため、子クラス側ではアルゴリズムの流れを気にせずに実装することができました。

テンプレートメソッドでアルゴリズムが共通化されているため、仮にアルゴリズムが間違えていたとしてもテンプレートメソッドクラスだけを修正すればよいので、効率的にプログラムを修正することができます。

プログラムで不具合を少なくするためには、同じ処理を何度も書かないということが大切ですので、せひテンプレートメソッドを使用してプログラムの共通化を行っていきましょう。