カナデKanade

19. プロトタイプパターン (Prototype)

しっかり整えた見本オブジェクトに自分を複製させて、クラスを知らなくてもそっくりなオブジェクトを作るプロトタイプパターンについて学びましょう。

ひとことで言うと
新しいオブジェクトを一から作らずに、しっかり整えておいた見本のオブジェクトを複製するパターンです。写す仕事は外ではなく、見本自身がします。

問題: 外から欄を1つずつ書き写すコード

ゲームにモンスターを出すとしましょう。モンスターごとに名前、体力、技のリストがあり、ボスモンスターには手下の数もあります。1回しっかり整えておいた見本とそっくりなモンスターを、何匹も出したいです。

まず思いつくのは、見本の欄を読んで、新しいモンスターのコンストラクターにそのまま渡す方法です。

【モンスター】を設計しよう:
    『名前』を「」にしよう
    『体力』を0にしよう
    『技たち』を【】にしよう

    最初に作られる時(【文字列】の『名前』, 【数字】の『体力』, 『技たち』)次のようにしよう:
        『私』の『名前』を『名前』にしよう
        『私』の『体力』を『体力』にしよう
        『私』の『技たち』を『技たち』にしよう

    〈紹介する〉を作ろう():
        枠「{『私』の『名前』}(体力 {『私』の『体力』}) 技: {『私』の『技たち』}」を出力しよう

【ボスモンスター】を設計しよう:
    『名前』を「」にしよう
    『体力』を0にしよう
    『技たち』を【】にしよう
    『手下の数』を0にしよう

    最初に作られる時(【文字列】の『名前』, 【数字】の『体力』, 『技たち』, 【数字】の『手下の数』)次のようにしよう:
        『私』の『名前』を『名前』にしよう
        『私』の『体力』を『体力』にしよう
        『私』の『技たち』を『技たち』にしよう
        『私』の『手下の数』を『手下の数』にしよう

    〈紹介する〉を作ろう():
        枠「{『私』の『名前』}(体力 {『私』の『体力』}、手下 {『私』の『手下の数』}) 技: {『私』の『技たち』}」を出力しよう

(参考:しっかり整えておいた見本のモンスター)
『スライム見本』を新しい【モンスター】(「スライム」, 30, 【「体当たり」】)にしよう
『キング見本』を新しい【ボスモンスター】(「スライムキング」, 300, 【「体当たり」, 「分裂」】, 3)にしよう

(参考:見本を写すには、外から欄を1つずつ書き写さなければなりません。クラスごとに書き写し方も違います)
『スライム1』を新しい【モンスター】(『スライム見本』の『名前』, 『スライム見本』の『体力』, 『スライム見本』の『技たち』)にしよう
『キング1』を新しい【ボスモンスター】(『キング見本』の『名前』, 『キング見本』の『体力』, 『キング見本』の『技たち』, 『キング見本』の『手下の数』)にしよう

(参考:スライム1だけが新しい技を覚えたのに…)
『スライム1』の『技たち』に「ねばねば飛ばし」を追加しよう

『スライム見本』の〈紹介する〉()を実行しよう
『スライム1』の〈紹介する〉()を実行しよう
『キング1』の〈紹介する〉()を実行しよう

実行結果
スライム(体力 30) 技: [体当たり, ねばねば飛ばし]
スライム(体力 30) 技: [体当たり, ねばねば飛ばし]
スライムキング(体力 300、手下 3) 技: [体当たり, 分裂]

おかしいですね。新しい技を覚えたのは『スライム1』だけなのに、見本まで「ねばねば飛ばし」を覚えてしまいました。これからこの見本で作るスライムは、みんなねばねばを飛ばします。

  • リストを一緒に使ってしまいます。『スライム見本』の『技たち』を渡すと、リストは新しく作られず、同じリスト1つを2人で一緒に持つことになります。片方に追加すると、もう片方も変わります。
  • 写す側が見本の中身を全部知らなければなりません。 どんな欄があるか、コンストラクターにどの順番で渡すかを外が知っている必要があります。モンスターに欄が1つ増えたら、写している場所を全部探して直すことになります。
  • クラスごとに写し方が違います。【モンスター】と【ボスモンスター】はコンストラクターが違うので、写す行も別々に書かなければなりません。見本が何のクラスか分からなければ、写すことさえできません。
  • 隠した欄は写せません。 クラスが『手下の数』を隠していたら、外からは読めないので書き写すこともできません。

解決策: 見本に「自分をもう1つ作って」と頼む

プロトタイプパターンは、写す仕事を外から取り出して、見本自身にまかせます。

  1. 原型の約束を決めます。「私とそっくりなものを、もう1つ作って」という〈複製する〉1つで十分です。
  2. クラスごとに〈複製する〉を作ります。自分の欄は自分がいちばんよく知っているので、隠した欄までもれなく写し、リストは新しいリストに移し入れます。
  3. しっかり整えた見本たちに名札をつけて、図鑑に集めておきます。
  4. 使う側は、図鑑に名前を言うだけで複製を受け取ります。見本が何のクラスか知らなくてもかまいません。

たとえ: 合鍵屋さんの合鍵づくり

鍵をもう1本作りたい時、鍵のみぞの深さを1つずつ測って描いたりはしません。合鍵屋さんに元の鍵を渡せば、機械がみぞをそのままなぞって削り、そっくりな鍵を作ってくれます。鍵の中がどうなっているか知らなくてもかまいません。それに、合鍵にキーホルダーをつけても、元の鍵はそのままです。元の鍵こそが原型です。

だれが何をするの?

役割 この例では やること
原型の約束 【原型】(規定) 〈複製する〉を約束します
具体的な原型 【モンスター】、【ボスモンスター】 自分とそっくりな新しいオブジェクトを作って返します
原型の集まり 【モンスター図鑑】 見本に名札をつけて集めておき、取り出すたびに複製を渡します
使う側 最後の部分 名前で複製を受け取って使います

カナデで作る

同じモンスターたちを、プロトタイプパターンで作り直してみましょう。

(参考:ステップ1 — 原型の約束。「私とそっくりなものを、もう1つ作って」)
【原型】を規定しよう:
    〈複製する〉がなければならない()

(参考:ステップ2 — 自分の写し方は自分がいちばんよく知っています。技のリストは新しいリストに移し入れます)
【原型】に従う【モンスター】を設計しよう:
    『名前』を「」にしよう
    『体力』を0にしよう
    『技たち』を【】に隠そう

    最初に作られる時(【文字列】の『名前』, 【数字】の『体力』, 『技たち』)次のようにしよう:
        『私』の『名前』を『名前』にしよう
        『私』の『体力』を『体力』にしよう
        『私』の『技たち』を『技たち』にしよう

    〈複製する〉を作ろう():
        『新しい技たち』を【】にしよう
        『私』の『技たち』の『技』ごとに繰り返そう:
            『新しい技たち』に『技』を追加しよう
        新しい【モンスター】(『私』の『名前』, 『私』の『体力』, 『新しい技たち』)を返そう

    〈覚える〉を作ろう(【文字列】の『技』):
        『私』の『技たち』に『技』を追加しよう

    〈紹介する〉を作ろう():
        枠「{『私』の『名前』}(体力 {『私』の『体力』}) 技: {『私』の『技たち』}」を出力しよう

【原型】に従う【ボスモンスター】を設計しよう:
    『名前』を「」にしよう
    『体力』を0にしよう
    『技たち』を【】に隠そう
    『手下の数』を0に隠そう

    最初に作られる時(【文字列】の『名前』, 【数字】の『体力』, 『技たち』, 【数字】の『手下の数』)次のようにしよう:
        『私』の『名前』を『名前』にしよう
        『私』の『体力』を『体力』にしよう
        『私』の『技たち』を『技たち』にしよう
        『私』の『手下の数』を『手下の数』にしよう

    (参考:隠した『手下の数』も、クラスの中では読めるのでそのまま写します)
    〈複製する〉を作ろう():
        『新しい技たち』を【】にしよう
        『私』の『技たち』の『技』ごとに繰り返そう:
            『新しい技たち』に『技』を追加しよう
        新しい【ボスモンスター】(『私』の『名前』, 『私』の『体力』, 『新しい技たち』, 『私』の『手下の数』)を返そう

    〈覚える〉を作ろう(【文字列】の『技』):
        『私』の『技たち』に『技』を追加しよう

    〈紹介する〉を作ろう():
        枠「{『私』の『名前』}(体力 {『私』の『体力』}、手下 {『私』の『手下の数』}) 技: {『私』の『技たち』}」を出力しよう

(参考:ステップ3 — 見本たちに名札をつけて集めておく図鑑)
【モンスター図鑑】を設計しよう:
    『見本たち』を{}に隠そう

    〈登録する〉を作ろう(【文字列】の『名前』, 【原型】の『見本』):
        『私』の『見本たち』の『名前』を『見本』にしよう

    〈取り出す〉を作ろう(【文字列】の『名前』):
        『見本』を『私』の『見本たち』の『名前』にしよう
        『見本』の〈複製する〉()を返そう

(参考:ステップ4 — 見本を1回だけしっかり整えて図鑑に入れておき、必要なたびに写してきます)
『スライム見本』を新しい【モンスター】(「スライム」, 30, 【「体当たり」】)にしよう
『図鑑』を新しい【モンスター図鑑】()にしよう
『図鑑』の〈登録する〉(「スライム」, 『スライム見本』)を実行しよう
『図鑑』の〈登録する〉(「スライムキング」, 新しい【ボスモンスター】(「スライムキング」, 300, 【「体当たり」, 「分裂」】, 3))を実行しよう

『スライム1』を『図鑑』の〈取り出す〉(「スライム」)にしよう
『キング1』を『図鑑』の〈取り出す〉(「スライムキング」)にしよう

(参考:今度はスライム1だけが新しい技を覚えます)
『スライム1』の〈覚える〉(「ねばねば飛ばし」)を実行しよう

『スライム見本』の〈紹介する〉()を実行しよう
『スライム1』の〈紹介する〉()を実行しよう
『キング1』の〈紹介する〉()を実行しよう

実行結果
スライム(体力 30) 技: [体当たり]
スライム(体力 30) 技: [体当たり, ねばねば飛ばし]
スライムキング(体力 300、手下 3) 技: [体当たり, 分裂]

今度は、見本は「体当たり」1つだけを知っています。『スライム1』が覚えた技は、『スライム1』にだけ入りました。

ステップごとにもう一度

  • ステップ1 — 原型の約束。【原型】は〈複製する〉1つだけを約束します。図鑑は、見本がモンスターなのかボスなのか知らなくても、この約束だけを信じて複製を頼みます。
  • ステップ2 — 自分の写し方は自分で。【モンスター】の〈複製する〉は、技を『新しい技たち』という新しいリストに1つずつ移し入れてから、新しいモンスターに渡します。だから複製と見本がリストを一緒に使うことはありません。【ボスモンスター】は、隠した『手下の数』もクラスの中では読めるので、そのまま写します。欄が隠れていても、複製は問題ありません。
  • ステップ3 — 図鑑。【モンスター図鑑】は、名札と見本を辞書に集めておきます。〈取り出す〉は見本を探して〈複製する〉を呼び、見本のかわりに複製を返します。見本そのものは図鑑の外に出ません。
  • ステップ4 — 名前を言うだけ。 使う側は『図鑑』の〈取り出す〉(「スライムキング」)のように名前を言うだけです。コンストラクターに何をどの順番で渡すのか、何のクラスなのか知らなくてもかまいません。

リストはどうして新しく入れ直すの?
『私』の『技たち』をそのまま渡すと、リストは写されず、同じリストを指す名札がもう1つ増えるだけです。外側だけ写して、中身は一緒に使っているわけです。複製が見本と別々に動くには、リストや辞書のように中身が変わるものは、新しく作って移し入れる必要があります。このように中身まで新しく作る複製を深い複製といいます。

複製を外にまかせると、隠した欄の前で止められます。だから複製はクラスの中でする必要があります。

【ボスモンスター】を設計しよう:
    『名前』を「」にしよう
    『手下の数』を0に隠そう

    最初に作られる時(【文字列】の『名前』, 【数字】の『手下の数』)次のようにしよう:
        『私』の『名前』を『名前』にしよう
        『私』の『手下の数』を『手下の数』にしよう

『キング見本』を新しい【ボスモンスター】(「スライムキング」, 3)にしよう

(参考:外から欄を書き写そうとしても、隠した欄は読めません)
『キング1』を新しい【ボスモンスター】(『キング見本』の『名前』, 『キング見本』の『手下の数』)にしよう

新しいモンスターを足してみると

「体力50の怒ったスライム」が必要になったとしましょう。

  • 前のやり方: 新しい見本を作り、その見本を写す場所ごとに、コンストラクターへ欄を1つずつ書き写す行を書かなければなりません。
  • プロトタイプパターン: 見本を1つ作って『図鑑』の〈登録する〉(「怒ったスライム」, …)で入れておけば終わりです。取り出して使う側は〈取り出す〉(「怒ったスライム」)の1行です。

やってみよう

  1. 体力50で「体当たり」「頭突き」を知っている「怒ったスライム」の見本を図鑑に登録して、2匹取り出して紹介してみてください。
  2. 【モンスター】の〈複製する〉で、『新しい技たち』のかわりに『私』の『技たち』をそのまま渡すように変えてみてください。そのあと『スライム1』に技を覚えさせて、見本がどうなるか確かめてみてください。

どんな時に使うといいの?

  • 設定することが多いオブジェクトを1回しっかり整えて、そっくりなものをいくつも作りたい時
  • 作るオブジェクトのクラスを使う側が知らない時(図鑑から名前で取り出す)
  • オブジェクトに隠した欄があって、外からは写せない時
  • 一から作るのに時間がかかるオブジェクトを(ファイルを読んで整えた設定のように)すばやくもう1つ作りたい時

いいところと気になるところ

いいところ 気になるところ
使う側がクラスやコンストラクターを知らなくても、そっくりなオブジェクトが手に入ります クラスごとに〈複製する〉を自分で作らなければなりません
隠した欄までもれなく写せます リストの中にオブジェクトがあるように中身が深いと、どこまで新しく作るか決めにくいです
見本を取りかえるだけで、新しい種類を作れます お互いを指し合うオブジェクトは、複製しているうちに終わらなくなることがあるので注意が必要です

早すぎるパターンは毒です。
オブジェクトを作るのに必要な値が2つか3つだけなら、コンストラクターを呼ぶほうが単純です。同じ設定をあちこちで同じようにくり返していたり、クラスを知らないままオブジェクトを作る必要が出てきた時に、プロトタイプを取り出しても遅くありません。

ほかのパターンとの関係

  • ファクトリーパターンとやることは同じです。 どちらも使う側のかわりにオブジェクトを作ってくれます。ファクトリーは「どのクラスで作るか」をコードで決め、プロトタイプは「この見本のように」とオブジェクトを見せて決めます。
  • メメントパターンと似たところがあります。 どちらもオブジェクトを写し、どちらも隠した欄があるので、写す仕事をクラスの中でします。メメントは「あとで元に戻す」ために状態だけを写し、プロトタイプは「もう1つ使う」ためにオブジェクトをまるごと写します。
  • コンポジットパターンと一緒に使えます。 箱の中に箱が入った形を複製するには、箱の〈複製する〉が、中に入っているものの〈複製する〉を順に呼びます。
プレイグラウンド
結果
コードを書いて「実行」ボタンを押してください。