背景
デザインパターンの本を読んで、わかったつもりになっていても、いざ使おうとしたときに使えない。
いたずらにパターンを適用しようとしても逆効果になるかもしれない。
使いこなすためには、どんなときにパターンを適用すれば良いか理解したうえで、応用できないといけない。
なので、使えそうなときはどんなときか身近な例を考えて作ってみました。
参考資料
Abstract Factory 解釈
- 直訳では「抽象的な工場」ですが、いろんな部品を組み合わせて一つの製品を作るときに有効なパターンで、部品は固定であるときが有効
- 逆にいちいち部品が変わる場合は使わない方が良い
僕の身の回りでどんなケースがあるか考えたときに、
サッカーは11人でやるもので監督が戦術を決めて、選手がプレーすることは変わらない、というのを題材にしてみようと思いました。
クラス
サッカーチームファクトリーがAbstract Factoryクラス、
FW、監督クラスは抽象部品クラスです。
サンフレファクトリークラス、日本代表クラスが具体的なファクトリークラス
ピエロス・ソティリウ、堂安、スキッベ、森保が具体的な部品クラスです。
今年のサンフレッチェは、なんといっても2022年度最優秀監督賞を取ったスキッベ監督ですよね。
FWのピエロスですが、今年はルヴァン杯での決勝戦でヒーローになりましたが、来年はリーグ戦での活躍を期待してます。
※クラス図はGitMindで作成
実装(抽象クラス)
#pragma once
/* Abstract */
class abstract_FW{
public:
virtual void shoot() = 0;
virtual ~abstract_FW(){}
};
/* Abstract */
class abstract_Manager{
public:
virtual void tactics() = 0;
virtual ~abstract_Manager(){}
};
/* Abstract */
class soccerteam_Abstractfactory{
public:
virtual abstract_FW *CreatePlayer() = 0;
virtual abstract_Manager *CreateManager() = 0;
virtual ~soccerteam_Abstractfactory(){};
};
実装(具象クラス)
#pragma once
/* Concrete */
class PIEROS_SOTIRIOU : public abstract_FW{
public:
void shoot();
~PIEROS_SOTIRIOU(){}
};
/* Concrete */
class Skibbe : public abstract_Manager{
public:
void tactics();
~Skibbe(){}
};
/* Concrete */
class Sanfrecce : public soccerteam_Abstractfactory{
public:
abstract_FW *CreatePlayer();
abstract_Manager *CreateManager();
~Sanfrecce(){}
};
#include <iostream>
#include "Abstract.h"
#include "Sanfrecce.h"
/* Concrete */
void PIEROS_SOTIRIOU::shoot(){
std::cout << "PIEROS_SOTIRIOU:shoot\r\n";
}
/* Concrete */
void Skibbe::tactics(){
std::cout << "Skibbe:3-6-1\r\n";
}
/* Concrete */
abstract_Manager *Sanfrecce::CreateManager(){
return new Skibbe();
}
abstract_FW *Sanfrecce::CreatePlayer(){
return new PIEROS_SOTIRIOU();
}
#pragma once
/* Concrete */
class Doan : public abstract_FW{
public:
void shoot();
~Doan(){}
};
/* Concrete */
class Moriyasu : public abstract_Manager{
public:
void tactics();
~Moriyasu(){}
};
/* Concrete */
class Japan : public soccerteam_Abstractfactory{
public:
abstract_FW *CreatePlayer();
abstract_Manager *CreateManager();
~Japan(){}
};
#include <iostream>
#include "Abstract.h"
#include "Japan.h"
/* Concrete */
void Doan::shoot(){
std::cout << "Doan:shoot\r\n";
}
/* Concrete */
void Moriyasu::tactics(){
std::cout << "Moriyasu:5-4-1\r\n";
}
/* Concrete */
abstract_Manager *Japan::CreateManager(){
return new Moriyasu();
}
abstract_FW *Japan::CreatePlayer(){
return new Doan();
}
#include <iostream>
#include "Abstract.h"
#include "Sanfrecce.h"
#include "Japan.h"
int main()
{
int team;
soccerteam_Abstractfactory *factory;
std::cout << "Enter team(0:sanfrecce, 1:Japan)";
std::cin >> team;
switch(team){
case 0:
factory = new Sanfrecce();
break;
case 1:
factory = new Japan();
break;
}
/* ここからは変わらない、具象クラスは意識していない */
if (factory != NULL){
abstract_FW * fw = factory->CreatePlayer();
abstract_Manager * mg = factory->CreateManager();
mg->tactics();
fw->shoot();
delete fw;
delete factory;
}else{
std::cerr << "error";
}
return 0;
}
実行結果
% g++ -o AbstractFactory Sanfrecce.cpp main.cpp Japan.cpp
% ./AbstractFactory
Enter team(0:sanfrecce, 1:Japan)0
Skibbe:3-6-1
PIEROS_SOTIRIOU:shoot
% ./AbstractFactory
Enter team(0:sanfrecce, 1:Japan)1
Moriyasu:5-4-1
Doan:shoot
サンフレッチェの場合は、スキッベ監督の戦術3-6-1と、ピエロスがシュートする、という結果が表示されます。
日本代表の場合は、森保監督の戦術5-4-1と、堂安がシュートする、という結果が表示されます。
総括
今回のキモとなる部分は、
抽象クラスであるsoccerteam_Abstractfactory型でインスタンスを生成後は、
具体的なクラスを意識せずに、監督の戦術と、FWのシュートというメソッドが呼び出せていることにあります。
このように、たとえば他のJリーグのチームに置き換えても同じ部品(プレイヤーと監督)であることは変わらないので、今回のデザインパターンが有効であると思いました。
他のデザインパターンでも例を考えて作ってみたいと思います。