ソフトウェア開発では、処理の流れが同じだが細部の内容だけが異なるといった場面が現れるかと思います。
そんな時にTemplateMethodパターンを利用することで、流れを再利用しつつも異なる部分だけを美しく差し替えることが出来ます。
本記事ではC++のコンソールアプリケーションをつかってTemplateMethodパターンの簡単なコードを紹介します。
前提知識
-
C++の基礎文法を理解している
TemplateMethodパターンとは
TemplateMethodパターンとは、処理の流れを基底クラスで定義し、細部の実装を派生クラスに委ねることで、アルゴリズム全体の一貫性を保ちながら柔軟な拡張を可能にするデザインパターンです。
フロー自体を派生先でそのまま使用するため、非常に再利用性が高いパターンであるといえます。
その反面で基底クラスへの依存度が高く、柔軟性があまり高くないことはデメリットとして存在します。
コード例
Actorクラスを基底クラスにして、プレイヤーと敵をつくるコード
//-------------------------------------------------------------------------
//! @file Actor.h
//! @brief Actorクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#pragma once
//-------------------------------------------------------------------------
//! @brief Actorクラス
//! @プレイヤーや敵キャラクターの基底クラス
//-------------------------------------------------------------------------
class Actor {
public:
//--------------------------------------------------------
// 仮想デストラクタ
//--------------------------------------------------------
virtual ~Actor() = default;
//--------------------------------------------------------
// テンプレートメソッド: 行動の流れを固定
//--------------------------------------------------------
void Act();
protected:
//--------------------------------------------------------
//! @brief 派生クラスでオーバーライドしてアクションを定義
//--------------------------------------------------------
virtual void Action() = 0;
private:
//--------------------------------------------------------
// 登場
//--------------------------------------------------------
void Appear();
//--------------------------------------------------------
// 退場処理
//--------------------------------------------------------
void Disappear();
};
//-------------------------------------------------------------------------
//! @file Actor.cpp
//! @brief Actorクラスの実装
//! @author つきの
//-------------------------------------------------------------------------
#include "Actor.h"
#include <iostream>
//-------------------------------------------------------------------------
//! @biref テンプレートメソッド: 行動の流れを固定
//-------------------------------------------------------------------------
void Actor::Act() {
Appear(); // 共通: 登場演出
Action(); // 差し替え必須: 行動内容
Disappear(); // 共通: 退場演出
}
//-------------------------------------------------------------------------
//! @biref 登場処理
//-------------------------------------------------------------------------
void Actor::Appear() {
std::cout << "[Actor] 登場!\n";
}
//-------------------------------------------------------------------------
//! @biref 退場処理
//-------------------------------------------------------------------------
void Actor::Disappear() {
std::cout << "[Actor] 退場...\n";
}
//-------------------------------------------------------------------------
//! @file Player.h
//! @brief Playerクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#pragma once
#include "Actor.h"
//-------------------------------------------------------------------------
//! @brief Playerクラス
//! @Actorクラスを継承したプレイヤーキャラクター
//-------------------------------------------------------------------------
class Player : public Actor {
private:
//-------------------------------------------------------------------------
// 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Action() override;
};
//-------------------------------------------------------------------------
//! @file Player.cpp
//! @brief Playerクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#include "Player.h"
#include <iostream>
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Player::Action() {
std::cout << "[Player] 即席めんを食べた。\n";
}
//-------------------------------------------------------------------------
//! @file Slime.h
//! @brief Slimeクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#pragma once
#include "Actor.h"
//-------------------------------------------------------------------------
//! @brief Slimeクラス
//! @Actorクラスを継承したスライム(敵)
//-------------------------------------------------------------------------
class Slime : public Actor {
private:
//-------------------------------------------------------------------------
// 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Action() override;
};
//-------------------------------------------------------------------------
//! @file Slime.cpp
//! @brief Slimeクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#include "Slime.h"
#include <iostream>
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Slime::Action() {
std::cout << "[Slime] 仲間になりたそうにプレイヤーをみつめる。\n";
}
//-------------------------------------------------------------------------
//! @file main.cpp
//! @brief TemplateMethodパターンのサンプルコード
//! @author つきの
//-------------------------------------------------------------------------
#include "Player.h"
#include "Slime.h"
#include <vector>
#include <memory>
//エントリポイント
int main() {
// Actor のポインタで扱える(アップキャスト)
std::vector<std::unique_ptr<Actor>> actors;
actors.push_back(std::make_unique<Player>()); // Playerを生成して追加
actors.push_back(std::make_unique<Slime>()); // Slimeを生成して追加
// 全 Actor に行動させる
for (const auto& actor : actors) {
actor->Act();
}
return 0;
}
[Actor] 登場!
[Player] 即席めんを食べた。
[Actor] 退場...
[Actor] 登場!
[Slime] 仲間になりたそうにプレイヤーをみつめる。
[Actor] 退場...
正しく動かせたようです。
基底クラスに処理の流れを記述してください。
これがテンプレートメソッドとなり、派生クラスでも統一されるフローとなります。
//-------------------------------------------------------------------------
//! @biref テンプレートメソッド: 行動の流れを固定
//-------------------------------------------------------------------------
void Actor::Act() {
Appear(); // 共通: 登場演出
Action(); // 差し替え必須: 行動内容
Disappear(); // 共通: 退場演出
}
Action()を純粋仮想関数にすることで、派生クラス内でそれぞれの動作を記述することが出来ます。
//-------------------------------------------------------------------------
// 派生クラスでオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Action() override;
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Player::Action() {
std::cout << "[Player] 即席めんを食べた。\n";
}
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Slime::Action() {
std::cout << "[Slime] 仲間になりたそうにプレイヤーをみつめる。\n";
}
テンプレートメソッドの実装は基底クラス内で行っているため、アップキャストすることによって、派生先をまとめて処理を記述することが可能になります。
// 全 Actor に行動させる
for (const auto& actor : actors) {
actor->Act();
}
総括
-
TemplateMethodパターンを利用することで、流れを再利用しつつ異なる実装のみを派生先のそれぞれで美しく実装することが可能となる - 流れを守る設計には強い反面、流れを柔軟に設計したい場合には少し不向きである。
-
アップキャストが使用できるため、呼び出し側のコードも非常に簡潔なつくりとなる