2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C++】TemplateMethodパターンで処理の流れを再利用しよう

Posted at

ソフトウェア開発では、処理の流れが同じだが細部の内容だけが異なるといった場面が現れるかと思います。
そんな時にTemplateMethodパターンを利用することで、流れを再利用しつつも異なる部分だけを美しく差し替えることが出来ます。
本記事ではC++のコンソールアプリケーションをつかってTemplateMethodパターンの簡単なコードを紹介します。

前提知識

  • C++の基礎文法を理解している

TemplateMethodパターンとは

TemplateMethodパターンとは、処理の流れを基底クラスで定義し、細部の実装を派生クラスに委ねることで、アルゴリズム全体の一貫性を保ちながら柔軟な拡張を可能にするデザインパターンです。
フロー自体を派生先でそのまま使用するため、非常に再利用性が高いパターンであるといえます。
その反面で基底クラスへの依存度が高く、柔軟性があまり高くないことはデメリットとして存在します。

コード例

Actorクラスを基底クラスにして、プレイヤーと敵をつくるコード

Actor.h
//-------------------------------------------------------------------------
//! @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();
};
Actor.cpp
//-------------------------------------------------------------------------
//! @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";
}
Player.h
//-------------------------------------------------------------------------
//! @file	Player.h
//! @brief  Playerクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#pragma once
#include "Actor.h"
//-------------------------------------------------------------------------
//! @brief Playerクラス
//! @Actorクラスを継承したプレイヤーキャラクター
//-------------------------------------------------------------------------
class Player : public Actor {
private:
    //-------------------------------------------------------------------------
	// 基底クラスをオーバーライドしてアクションを定義
    //-------------------------------------------------------------------------
    void Action() override;
};
Player.cpp
//-------------------------------------------------------------------------
//! @file	Player.cpp
//! @brief  Playerクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#include "Player.h"
#include <iostream>
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Player::Action() {
	std::cout << "[Player] 即席めんを食べた。\n";
}
Slime.h
//-------------------------------------------------------------------------
//! @file	Slime.h
//! @brief  Slimeクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#pragma once
#include "Actor.h"
//-------------------------------------------------------------------------
//! @brief Slimeクラス
//! @Actorクラスを継承したスライム(敵)
//-------------------------------------------------------------------------
class Slime : public Actor {
private:
    //-------------------------------------------------------------------------
    // 基底クラスをオーバーライドしてアクションを定義
    //-------------------------------------------------------------------------
    void Action() override;
};
Slime.cpp
//-------------------------------------------------------------------------
//! @file	Slime.cpp
//! @brief  Slimeクラスの定義
//! @author つきの
//-------------------------------------------------------------------------
#include "Slime.h"
#include <iostream>
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Slime::Action() {
	std::cout << "[Slime] 仲間になりたそうにプレイヤーをみつめる。\n";
}
main.cpp
//-------------------------------------------------------------------------
//! @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;
}
result
[Actor] 登場!
[Player] 即席めんを食べた。
[Actor] 退場...
[Actor] 登場!
[Slime] 仲間になりたそうにプレイヤーをみつめる。
[Actor] 退場...

正しく動かせたようです。

基底クラスに処理の流れを記述してください。
これがテンプレートメソッドとなり、派生クラスでも統一されるフローとなります。

Actor.h
//-------------------------------------------------------------------------
//! @biref テンプレートメソッド: 行動の流れを固定
//-------------------------------------------------------------------------
void Actor::Act() {
	Appear();        // 共通: 登場演出
	Action();        // 差し替え必須: 行動内容
	Disappear();     // 共通: 退場演出
}

Action()を純粋仮想関数にすることで、派生クラス内でそれぞれの動作を記述することが出来ます。

Actor.h
    //-------------------------------------------------------------------------
	// 派生クラスでオーバーライドしてアクションを定義
    //-------------------------------------------------------------------------
    void Action() override;
Player.cpp
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Player::Action() {
	std::cout << "[Player] 即席めんを食べた。\n";
}
Slime.cpp
//-------------------------------------------------------------------------
//! @brief 基底クラスをオーバーライドしてアクションを定義
//-------------------------------------------------------------------------
void Slime::Action() {
	std::cout << "[Slime] 仲間になりたそうにプレイヤーをみつめる。\n";
}

テンプレートメソッドの実装は基底クラス内で行っているため、アップキャストすることによって、派生先をまとめて処理を記述することが可能になります。

main.cpp
	// 全 Actor に行動させる
	for (const auto& actor : actors) {
		actor->Act();
	}

総括

  • TemplateMethodパターンを利用することで、流れを再利用しつつ異なる実装のみを派生先のそれぞれで美しく実装することが可能となる
  • 流れを守る設計には強い反面、流れを柔軟に設計したい場合には少し不向きである。
  • アップキャストが使用できるため、呼び出し側のコードも非常に簡潔なつくりとなる
2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?