Prptotypeパターンは生成に関するデザインパターンの1つです。 通常はnewしてインスタンスを生成しますが、Prototypeでは既存のインスタンスをコピー(clone)して新しいインスタンスを生成します。
メリット
新しいインスタンスを作成する複雑さを、クライアントから隠蔽する。
型のわからないオブジェクトの生成を行うという選択肢を、クライアントに提供する。
環境においてはオブジェクトのコピーの方が新しいオブジェクトを作成するよりも有効である可能性がある(ソースコードの管理等)。
デメリット
コピーを作る事で物事が複雑になる場合がある(これについては追々書いてみたい)。
Head First デザインパターンでモンスターを作るクラス図があったのでそれを参考に中身だけ書いてみたいと思います。
単純にモンスターをコピーできるようにするのと、ゲームを楽しむユーザーがモンスターのレベルとHPを変更できるようにしていきます。
・Monster.php
<?php
abstract class Monster
{
private int $id; // 1種類につき与えられるモンスターのid
private string $name;
protected int $level;
protected int $hp;
private string $category;
private bool $copied; // コピーされたものかどうか
public function __construct(int $id, string $name, int $level, int $hp, string $category)
{
$this->id = $id;
$this->name = $name;
$this->level = $level;
$this->hp = $hp;
$this->category = $category;
$this->copied = false;
}
abstract protected function __clone();
public function newInstance(): \Monster
{
return clone $this;
}
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function getLevel(): int
{
return $this->level;
}
public function getHp(): int
{
return $this->hp;
}
public function getCategory(): string
{
return $this->category;
}
public function getCaller(): string
{
return $this->caller;
}
protected function copied(bool $copied): void
{
$this->copied = $copied;
}
}
ユーザー側で値を変更できないモンスター
・WellKnownMonster.php
<?php
require_once 'Monster.php';
class WellKnownMonster extends Monster
{
protected function __clone()
{
$this->copied(true);
}
}
ユーザー側でlevelとhpを変更できるモンスター
・DynamicPlayerGeneratedMonster.php
<?php
require_once 'Monster.php';
class DynamicPlayerGeneratedMonster extends Monster
{
protected function __clone() {
$this->copied(true);
}
public function setLevel(int $level): void
{
$this->level = $level;
}
public function setHp(int $hp): void
{
$this->hp = $hp;
}
}
・MonsterRegistry.php
<?php
class MonsterRegistry
{
private array $registered;
public function register(Monster $monster): void
{
$this->registered[$monster->getId()] = $monster;
}
public function getMonster(int $id){
return $this->registered[$id]->newInstance();
}
}
・ index.php
<?php
require_once 'MonsterRegistry.php';
require_once 'WellKnownMonster.php';
require_once 'DynamicPlayerGeneratedMonster.php';
$monsterRegistry = new MonsterRegistry();
$monster1 = new WellKnownMonster(1, 'ゴブリン', 4, 12, 'ゴブリン');
$monsterRegistry->register($monster1);
$monsterCopy1 = $monsterRegistry->getMonster($monster1->getId());
var_dump($monster1);
var_dump($monsterCopy1);
$monster2 = new DynamicPlayerGeneratedMonster(37, 'アイスボム', 24, 400, 'ボム');
$monsterRegistry->register($monster2);
$monsterCopy2 = $monsterRegistry->getMonster($monster2->getId());
$monsterCopy2->setLevel(99);
$monsterCopy2->setHp(9999);
var_dump($monster2);
var_dump($monsterCopy2);
コピー元となるモンスターはDBに入れそうなものですが…。 その場合は引っ張ってきてインスタンス化して必要であればコピーしていくといった方法で使うといいかな?って思います。
(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
13章 付録:残りのパターン