Prototypeパターンとプロトタイプについて調べてみる

はいどーもこんにちは、プロトタイプニートの無職と申します。
本日はPrototypeパターンについて勉強していきたいと思いますよ。
あとついでにプロトタイプという考え方についても調べていこうと思います。

Prototypeパターンとは

あるインスタンスをプロトタイプとして使い、新しいオブジェクトを作るのがPrototypeパターンです。
ツクラーさんなら「プロトタイプって、JavaScriptのアレ?」と思うかもしれません。
実際、JavaScriptのprototypeオブジェクトはこのPrototypeパターンと関係あるようです。
しかしそれはそれとして、Prototypeパターンについてはまずは見ていきます。

Prototypeパターンは「あるインスタンスをプロトタイプとして使い、新しいオブジェクトを作る」と書きました。
これが必要になりそうなシチュエーションはどんなときがあるでしょうか?
例えば複雑なオブジェクトを作成したあと、それと同じオブジェクトをもう一度作りたい場合に使えそうだなと思いました。
ゲームで言えば、Monsterクラスを一つ作成しておいて、それにnameやatkやspeedやらを設定し、同じモンスターを複製するときにPrototypeパターンを使って新しく同じモンスターを作る、という感じです(まあUnityなら普通にScriptableObject使いそうですが)。
絵を描くツールなら、ユーザーが描画した(あるいはソフトにあらかじめ用意されている)図形をコピーするときに使えるみたいです。

クラスの動き

以上はデザインパターンとしてのPrototypeでした(具体的なコードはググればたくさん出てくると思うので省略)。
ここからは言語としてのプロトタイプを見ていこうと思います。

クラスを作成したとき、メソッドは全てのインスタンスで共通していますよね。
言い換えると、インスタンスは自分自身のクラスをたぐり寄せ、必要なメソッドを実行することになります。
これに対して、フィールドの値は各インスタンスで変わるはずです。
そうでなければ、インスタンスを作成する意味がありません。

注*なおstaticクラスや継承については考えていません。

プロトタイプの動き

ではJavaScriptはどうでしょうか?
JavaScriptはよく「クラスは存在しない」なんて言われますよね。
実際、クラスは存在せず、クラスの役割を果たすようなオブジェクトが存在しているだけです。
例えば以下のコードは完全に有効です。

多くの方は「おや? Enemyを宣言している中に関数が含まれているぞ」と思うのではないでしょうか。
普通はこういう書き方はしません。
なぜなら、Enemyインスタンスを生成するたびにthis.attackフィールド(関数オブジェクト)が作成され、メモリの無駄だからです。
this.attackフィールド(関数オブジェクト)はインスタンスごとに振る舞いを変えないので、これは一つだけ生成すればよいのですね。
そこでprototypeオブジェクトです。

先ほどと全く同じ動作をしますが、インスタンスを生成してもattackフィールドは作成されなくなりました。
代わりに、自身に存在しないプロパティ(ここではattackメソッド)にアクセスしようとしたとき、prototypeオブジェクトをチェックし、そこに存在するattackメソッドを実行するようになります。
このprototypeオブジェクトは、その型が共有する別のオブジェクトです。

では例えば以下のコードはなんと出力されるでしょうか?

答えは「プロトタイプではない」方です。
なぜなら、prototypeオブジェクトのチェックはあくまでも「自身が保持するプロパティ内で見つからなかったら」という制約があるからです。

では次のコードはどうなるでしょう?

1,1,1と表示されてほしいのですが、実際には1,2,2と表示されます。
これは全てのインスタンスがprototypeオブジェクトが保持する共通のdeadCountオブジェクトを参照しているためです。
これは短いコードなのでパッと見たら不具合の原因がわかりますが、数千行に及ぶコードの中でこんな風に共有オブジェクトを使われたら、原因を探るのにめちゃんこ時間がかかります(特に他人が書いたコード)。
なので特別な理由がない限り僕はこういう書き方は絶対にしませんし、してほしくもないですw
prototypeは関数に対して使うのがよさげですね。

クラスとの共通点

さてJavaScriptのプロパティやプロトタイプですが、トリッキーな書き方をしない場合、めちゃんこクラスと似ていますよね。
具体的に言えばクラスは「フィールドは各インスタンスが持っていて、メソッドは自身のクラスから探す」のに対し、JavaScriptは「フィールドは各オブジェクト(インスタンスに相当)が持っていて、メソッドはその型に共通するprototypeオブジェクトから探す」ということです。
JavaScriptを理解するとき、クラスを想定して理解しても全然問題なさそうに感じるのはこのためではないでしょーか。

プロトタイプという考え方

プロトタイプは「自分が持っていなければ、prototypeオブジェクトから探す」ということでした。
これをデータモデリングに応用しよう、ということが『Game Programming Patterns』で紹介されていました。
例えばScriptableObjectで敵キャラを作っているとします。
このとき「スライム、ぶちスライム、キングスライム」とかなんとかスライム系の敵をたくさん作るとします。
で、どのスライム系も基本は「スライム」をもとにした能力を持っているとします(耐性とか)。
それを全てのスライムに設定していくのめんどくさいですよね。特に変更があったとき、全てのスライムの設定を変えなきゃいけないのは不具合の原因になるでしょう。
そこでプロトタイプです。
「自分が設定していないデータについては、プロトタイプを調べてそれを適用する」というようにすれば、例えば「スライムはきちんと全ての設定をしておく」「ぶちスライムについてはスライムから一部の耐性だけ変更する」「キングスライムは耐性はスライムと同じだが、能力は大幅に上昇させよう」とか簡単にできるわけですね。
大元のスライムの能力を変更すれば、スライム系全ての強さの変更もできちゃうわけです(もちろんスライムをプロトタイプに登録していれば、の話ですが)。

終わりに

JavaScriptを初めて触ったとき、プロトタイプについて全然意味がわからなかったのですが、こうして改めて調べてみるとよくできてるなあと思いました。
個人的にはプロトタイプベースよりはクラスベースの方が好きですが、データについてはプロトタイプの考え方を導入するのは普通にアリな気がしますね。
モンスターがたくさん登場する系のゲームを作るときはやってみようと思いますw

ほなそんな感じでまた。

フォローする