Featured image of post 第6回-これがPrototypeパターンだ! - mass-producing-monsters

第6回-これがPrototypeパターンだ! - mass-producing-monsters

実は作ってきたものが「Prototypeパターン」でした!GoFデザインパターンの生成パターンを学び、Factory Methodとの違いも解説します。

@nqounetです。

「PerlとMooでモンスター軍団を量産してみよう」シリーズの最終回です。

前回の振り返り

前回は、Storable::dclone()を使って「深いコピー」を実装し、ネストしたオブジェクトも完全に独立したコピーを作る方法を学びました。

今回は、これまで作ってきたものがPrototypeパターンというデザインパターンであることを明かします。

シリーズ全体の目次は以下をご覧ください。

シリーズ全体を振り返る

これまでの6回を通じて、以下のことを学んできました。

テーマ学んだこと
第1回new()の限界同じオブジェクトを大量に作るとコードが冗長に
第2回clone()の導入MooX::Cloneで既存オブジェクトをコピー
第3回バリエーション作成clone()後に属性を変更して派生を作成
第4回浅いコピーの罠ネストしたオブジェクトが共有される問題
第5回深いコピーStorable::dclone()で完全に独立したコピー
今回パターンの正体これがPrototypeパターンだった!

実は、これらはすべてPrototypeパターンを学ぶための布石でした。

Prototypeパターンとは

Prototypeパターンは、GoF(Gang of Four)デザインパターンの一つで、「生成パターン」に分類されます。

既存のオブジェクト(プロトタイプ)を複製(clone)することで、新しいオブジェクトを生成するパターン

コンストラクタ(new)を直接呼び出す代わりに、既存のオブジェクトをコピーして新しいオブジェクトを作るというアプローチです。

パターンの構成要素

Prototypeパターンは、以下の構成要素から成り立っています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────┐
│              Prototypeパターンの構造             │
└─────────────────────────────────────────────────┘

                    ┌─────────────┐
                    │  Prototype  │
                    │   (Role)    │
                    │─────────────│
                    │ clone()     │
                    └──────┬──────┘
                           │ requires
            ┌──────────────┼──────────────┐
            │              │              │
   ┌────────▼────────┐  ┌──▼───────┐  ┌───▼──────┐
   │ConcretePrototype│  │ Monster  │  │ Weapon   │
   │     (実装)      │  │ (Slime)  │  │ (Sword)  │
   │─────────────────│  │──────────│  │──────────│
   │ clone() {...}   │  │ clone()  │  │ clone()  │
   └─────────────────┘  └──────────┘  └──────────┘
            │ uses
   ┌────────┴────────┐
   │     Client      │
   │  (メインコード)  │
   │─────────────────│
   │ prototype.clone │
   └─────────────────┘
構成要素役割本シリーズでの対応
Prototypeclone()メソッドを要求するインターフェース(Role)Cloneableロール(概念上)
ConcretePrototypeclone()を実装した具象クラスMonster, Weapon
Clientプロトタイプをclone()して新オブジェクトを作るコードメインスクリプト

Factory Methodパターンとの違い

同じ「生成パターン」に分類されるFactory Methodパターンと比較してみましょう。

項目PrototypeパターンFactory Methodパターン
生成方法既存オブジェクトのクローンnewでインスタンス化
クラス階層不要(クローンするだけ)サブクラス化が必要
状態の引き継ぎ元オブジェクトの状態を継承新規に状態を設定
適用場面生成コストが高い、動的決定、テンプレートからの派生製品種類が固定、継承で拡張
Perl実装clone() + MooX::Clone / Storable::dclone()extends + オーバーライド

コードで比較

Prototypeパターン(本シリーズで学んだ方法):

1
2
3
4
5
6
# ベースオブジェクトを作成
my $base_slime = Monster->new(name => 'スライム', hp => 10, ...);

# clone()で複製
my $red_slime = $base_slime->clone;
$red_slime->color('赤');

Factory Methodパターン:

1
2
3
# ファクトリクラスでnew()を呼ぶ
my $factory = SlimeFactory->new;
my $slime = $factory->create_monster;  # 内部でnew()を呼ぶ

Prototypeパターンは「既存オブジェクトをコピーする」のに対し、Factory Methodパターンは「ファクトリクラスがnew()でオブジェクトを作る」という違いがあります。

Prototypeパターンが有効な場面

Prototypeパターンは、以下の場面で特に有効です。

  1. 生成コストが高いオブジェクト

    • 初期化に時間がかかるオブジェクトは、一度作ってコピーしたほうが効率的
  2. 実行時に動的にオブジェクト種類を決定する

    • コンパイル時にどのクラスを使うか分からない場合、既存オブジェクトをコピーして対応
  3. テンプレートからのバリエーション生成

    • ベースオブジェクトを作り、少しずつ属性を変えたバリエーションを量産(本シリーズの主題)
  4. 複雑な初期化処理を回避したい

    • 設定済みオブジェクトをコピーすれば、初期化ロジックを再実行する必要がない

Moo::Roleで明示的にPrototypeを表現する

概念をより明確にするため、Roleを使ってclone()を強制するインターフェースを定義することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo, Storable(Perl標準モジュール)

use v5.36;
use Storable qw(dclone);

# Prototypeロール(clone()を要求)
package Cloneable {
    use Moo::Role;
    requires 'clone';
}

package Weapon {
    use Moo;
    use Storable qw(dclone);
    with 'Cloneable';  # Cloneableロールを適用

    has name  => (is => 'ro', required => 1);
    has power => (is => 'rw', required => 1);

    sub clone ($self) {
        return dclone($self);
    }
}

package Monster {
    use Moo;
    use Storable qw(dclone);
    with 'Cloneable';  # Cloneableロールを適用

    has name    => (is => 'ro', required => 1);
    has hp      => (is => 'rw', required => 1);
    has attack  => (is => 'rw', required => 1);
    has defense => (is => 'rw', required => 1);
    has weapon  => (is => 'rw');

    sub clone ($self) {
        return dclone($self);
    }

    sub show_status ($self) {
        say "【" . $self->name . "】HP:" . $self->hp
            . " 攻撃:" . $self->attack
            . " 防御:" . $self->defense;
    }
}

# 使用例
my $base = Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2);
my $copy = $base->clone;
$copy->show_status;

Cloneableロールがclone()メソッドを要求することで、「このクラスはPrototypeパターンに従っている」ことを明示できます。

シリーズのまとめ

このシリーズを通じて、以下のことを学びました。

技術的な学び

  • MooX::CloneでMooクラスにclone()メソッドを追加できる
  • clone()でオブジェクトを複製し、効率的に量産できる
  • 浅いコピーではネストしたオブジェクトが共有される問題がある
  • Storable::dclone()で深いコピーを実装し、完全に独立したコピーを作れる

パターン的な学び

  • 既存オブジェクトをコピーして新しいオブジェクトを作る手法は「Prototypeパターン」である
  • Prototypeパターンは GoF デザインパターンの「生成パターン」の一つである
  • Factory Methodとは異なり、クラス階層を必要としない柔軟なアプローチである

発展的な学習

このシリーズを終えた後、さらに以下のパターンを学んでみることをお勧めします。

  • Abstract Factoryパターン: 関連するオブジェクト群を一括で生成
  • Builderパターン: 複雑なオブジェクトを段階的に構築
  • Flyweightパターン: メモリ効率を重視したオブジェクト共有

これらを学ぶことで、デザインパターンの理解がより深まるでしょう。

参考資料

謝辞

最後までお読みいただきありがとうございました。このシリーズが、皆さんのPerlプログラミングとデザインパターンの学習に役立てば幸いです。

次のシリーズもお楽しみに!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。