開発を行っていると、ついついクラスのコンストラクタに渡す引数の数がどんどん増えて、画面からもはみ出る…なんてことがあると思います。
Builderパターンという手法を活用することで、その問題を回避しつつ、柔軟な開発が行えます。
本記事では、Builderパターンを用いて、コンソール上で実行できるサンプルコードを紹介します。
Builderパターンとは
Builderパターンとは、オブジェクトの構築を構造から分離することで、構築時に柔軟にパラメータの注入を行える設計のことです。
これを使用することで、コンストラクタが乱雑になるなどのトラブルを抑えることが可能になります。
コード例
ActorクラスをBuilderパターンで構築するサンプル
Actorは、RPGなどのゲームのキャラクターを想像して、サンプルコードを見ていただけるとよいと思います。
//----------------------------------------------------------------------
//! @file Actor.hpp
//! @brief アクター(キャラクター)情報を保持するクラス
//----------------------------------------------------------------------
#pragma once
#include <string>
//----------------------------------------------------------------------
//! @class ゲーム内のアクター(キャラクター)情報を保持するクラス
//----------------------------------------------------------------------
class Actor {
private:
std::string name; // 名前
int hp; // ヒットポイント
int attack; // 攻撃力
int defense; // 防御力
public:
//----------------------------------------------------------------------
// コンストラクタ(Builderから呼び出される)
//! @param name [in] アクターの名前
//! @param hp [in] アクターのヒットポイント
//! @param attack [in] アクターの攻撃力
//! @param defense [in] アクターの防御力
//----------------------------------------------------------------------
Actor(const std::string& name, int hp, int attack, int defense);
//----------------------------------------------------------------------
// アクターのステータスを表示するメソッド
//----------------------------------------------------------------------
void PrintStatus() const;
};
#include "Actor.hpp"
#include <iostream>
//----------------------------------------------------------------------
//! コンストラクタ(Builderから呼び出される)
//----------------------------------------------------------------------
Actor::Actor(const std::string& name, int hp, int attack, int defense)
: name(name), hp(hp), attack(attack), defense(defense) {
}
//----------------------------------------------------------------------
//! アクターのステータスを表示するメソッド
//----------------------------------------------------------------------
void Actor::PrintStatus() const {
std::cout << "名前: " << name << "\nHP: " << hp
<< "\n攻撃力: " << attack << "\n防御力: " << defense << std::endl;
}
//------------------------------------------------------------------------------
//! @file ActorBuilder.hpp
//! @brief Actorオブジェクトを段階的に構築するBuilderクラスの定義
//------------------------------------------------------------------------------
#pragma once
#include "Actor.hpp"
//----------------------------------------------------------------------
//! @class Actorオブジェクトを段階的に構築するBuilderクラス
//----------------------------------------------------------------------
class ActorBuilder {
private:
std::string name = "名無し"; // 初期名前
int hp = 100; // 初期HP
int attack = 10; // 初期攻撃力
int defense = 5; // 初期防御力
public:
//----------------------------------------------------------------------
// 名前を設定するメソッド
//! @param name [in] アクターの名前
//! @return 自身の参照
//----------------------------------------------------------------------
ActorBuilder& SetName(const std::string& name);
//----------------------------------------------------------------------
// HPを設定するメソッド
//! @param hp [in] アクターのヒットポイント
//! @return 自身の参照
//----------------------------------------------------------------------
ActorBuilder& SetHP(int hp);
//----------------------------------------------------------------------
// 攻撃力を設定するメソッド
//! @param attack [in] アクターの攻撃力
//! @return 自身の参照
//----------------------------------------------------------------------
ActorBuilder& SetAttack(int attack);
//----------------------------------------------------------------------
// 防御力を設定するメソッド
//! @param defense [in] アクターの防御力
//! @return 自身の参照
//----------------------------------------------------------------------
ActorBuilder& SetDefense(int defense);
//----------------------------------------------------------------------
// 最終的にActorオブジェクトを生成するメソッド
//! @return 構築されたActorオブジェクト
//----------------------------------------------------------------------
Actor Build() const;
};
//------------------------------------------------------------------------------
//! @file ActorBuilder.cpp
//! @brief Actorオブジェクトを段階的に構築するBuilderクラスの定義
//------------------------------------------------------------------------------
#include "ActorBuilder.hpp"
//----------------------------------------------------------------------
//! 名前を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetName(const std::string& name) {
this->name = name;
return *this;
}
//----------------------------------------------------------------------
//! HPを設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetHP(int hp) {
this->hp = hp;
return *this;
}
//----------------------------------------------------------------------
//! 攻撃力を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetAttack(int attack) {
this->attack = attack;
return *this;
}
//----------------------------------------------------------------------
//! 防御力を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetDefense(int defense) {
this->defense = defense;
return *this;
}
//----------------------------------------------------------------------
//! 最終的にActorオブジェクトを生成するメソッド
//----------------------------------------------------------------------
Actor ActorBuilder::Build() const {
return Actor(name, hp, attack, defense);
}
#include "ActorBuilder.hpp"
//メイン関数
int main() {
//------------------------------------------------------
// 直接Actorオブジェクトを構築
//------------------------------------------------------
Actor witch("魔法使い", 130, 20, 5);
// 魔法使いのステータスを表示
witch.PrintStatus();
//改行
printf_s("\n");
//------------------------------------------------------
// Builderを使ってActorオブジェクトを構築
//------------------------------------------------------
Actor thief = ActorBuilder()
.SetName("盗賊")
.SetHP(150)
.SetAttack(30)
.SetDefense(12)
.Build();
// 盗賊のステータスを表示
thief.PrintStatus();
// 改行
printf_s("\n");
//------------------------------------------------------
// Builderを使って柔軟に組み立ててみる
//------------------------------------------------------
Actor fighter = ActorBuilder()
.SetAttack(40)// 攻撃力のみ設定
.SetName("戦士")
.Build();
// 戦士のステータスを表示
fighter.PrintStatus();
// 終了
return 0;
}
名前: 魔法使い
HP: 130
攻撃力: 20
防御力: 5
名前: 盗賊
HP: 150
攻撃力: 30
防御力: 12
名前: 戦士
HP: 100
攻撃力: 40
防御力: 5
柔軟なパラメータの入れ込みができているようです。
Builderクラスは、構築したいクラスと同じパラメータを持ち、宣言時にデフォルトの値を入れておきます。
private:
std::string name = "名無し"; // 初期名前
int hp = 100; // 初期HP
int attack = 10; // 初期攻撃力
int defense = 5; // 初期防御力
各パラメータにセッタを作成しておき、各セッタで自身の参照を返すことで、メソッドチェーンで直感的な構築を実現することができます。
//----------------------------------------------------------------------
//! 名前を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetName(const std::string& name) {
this->name = name;
return *this;
}
//----------------------------------------------------------------------
//! HPを設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetHP(int hp) {
this->hp = hp;
return *this;
}
//----------------------------------------------------------------------
//! 攻撃力を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetAttack(int attack) {
this->attack = attack;
return *this;
}
//----------------------------------------------------------------------
//! 防御力を設定するメソッド
//----------------------------------------------------------------------
ActorBuilder& ActorBuilder::SetDefense(int defense) {
this->defense = defense;
return *this;
}
メソッドチェーンの最後に、構築したかったクラス(本記事ではActorクラス)のコンストラクタを呼び、それを返す関数にすることで、Builderクラスのパラメータを使ったクラスを構築することができます。
//----------------------------------------------------------------------
//! 最終的にActorオブジェクトを生成するメソッド
//----------------------------------------------------------------------
Actor ActorBuilder::Build() const {
return Actor(name, hp, attack, defense);
}
//------------------------------------------------------
// Builderを使ってActorオブジェクトを構築
//------------------------------------------------------
Actor thief = ActorBuilder()
.SetName("盗賊")
.SetHP(150)
.SetAttack(30)
.SetDefense(12)
.Build();
総括
-
Builderパターンを使用することで、コンストラクタの引数に縛られることなく柔軟なインスタンスの構築を行うことができる。 - メソッドチェーンの仕組みを使用することで、セッタをつなげるなど細かい実装ができ、柔軟な構築を手助けしてくれる。