クラスと工場。 プロトタイプを継承してオブジェクトを分解および組み立てる方法

こんにちは、Habra!
JavaScriptでゲームを開発する場合、多くの場合、多くのオブジェクトを作成する必要があります。 コードにdrれずに正しく行う方法については、1か月前にMinskのFrontend Dev Confで話しました。 このレポートは、会議に参加しておらず、多くのオブジェクトを作成する問題に直面している人、またはHTML5ゲームの開発者である人にとって興味深いものになるでしょう。



写真付きのカットテキストの下。


クラスと工場


前述のように、ゲームを作成するときは、さまざまなオブジェクトを作成する必要があります。 これを実現するために、プロトタイプ継承が使用されます。 このアプローチでの古典的なクラスの様子:

function animal() { ... } animal.prototype.left = function() { ... } animal.prototype.right = function() { ... } 

JavaScriptでOOPについて説明している多くの書籍で、この例や類似の例を見つけることができます。 同じ原則により、ほとんどのMVCフレームワークがコピーされます。 しかし、異なるクラスの多くのオブジェクトを取得する必要がある場合はどうすればよいでしょうか? 上記のアプローチでは、多くのクラスを作成する必要があります。 その結果、プロジェクトのその後のサポート、バグの発見、およびそれがどのように機能するかを理解することが困難になる場合があります。 他のオブジェクトからのいくつかのオブジェクトの依存継承の無限チェーンが作成されます。 この状況から抜け出す方法は、工場を使用することです。

C ++では、オブジェクトファクトリは、単一のインターフェイスを使用する特定のタイプのオブジェクトのみを作成できます。 C ++でのこのパターンの主な利点は、単一のインターフェイスを使用してさまざまなクラスのオブジェクトを作成する単純化です。 JavaScriptでは、この制限から離れて、完全に異なるプロパティとメソッドのセットを持つオブジェクトを1か所で取得できます。

アプリケーションの透過的な構造を構築するために、すべてのプロパティのリストとすべてのプロトタイプのリストをグループに分けて作成します。 例:

 var properties = { speed: { x: 0, y: 0 limit: { x: 10, y: 10 } }, acceleration: { x: 0, y: 0 }, live: { health: 100, killing: 0, level: 0 } }; 

 var prototypes = { left: function() { ... }, right: function() { ... } }; 

ファクトリ内のオブジェクトを注文するには、オブジェクトが継承するプロパティのリストとプロトタイプのリストを指定するだけです。 例:

 var objectA = factory([ "physics", "live" ], [ "left" ]); var objectB = factory([ "live", "acceleration" ], [ "right" ]); 

工場で何が起こりますか? 以下について:

 var object = {}; //   for(var name in properties) { object[name] = properties[name]; } //   for(var method in prototypes) { object.prototype[method] = prototype[method]; } 




実際、実際のメカニズムはもう少し複雑になります。 たとえば、ネストされたオブジェクトの存在のプロパティをソートし、いくつかの標準値を割り当て、入力データの有効性を確認し、要求されたクラスを動的に作成、保存、取得する方法を学習する必要がありますが、主な本質は変わりません。

最後の問題-すべてのクラスとオブジェクトのプロパティを簡単な方法で美しく説明すること-を解決することが残っています。 これは、すべて同じリストを使用して実行できます。 ブロッククラスの例を使用してこれを検討してください。

 var classList = { ... block: { _properties: [ //  ,    "skin", "dimensions", "physics", "coordinates", "type" ], _prototypes: [], //  ,    _common: { //     _properties: { move: false //  move     false } }, floor: { //       roughness: 0.37 //     }, gold: { roughness: 0.34 }, sand: { roughness: 0.44 //   ,    , }, //    water: { roughness: 0.25 } } }; 

クラスとオブジェクトの同様のリストを使用して、APIを自動的に作成することもできます。 例:

 block.gold(); //    block.sand(); //    block.water(); //    

したがって、ゲーム内のオブジェクトの数と種類、およびJSONタイプの3つのリストに関係なく、コードサイズが変わらないファクトリーが得られました。



リストは簡単に変更でき、単にドキュメントで覆われているだけでなく、オブジェクトの数が増えてもコードの量は増えません(リストは構成のため)ので便利です。 ファクトリコードを記述すると、混乱することなく、透明な構造を持つ何百ものさまざまなオブジェクトを作成できることがわかりました。

プロパティドキュメントファイルの例:

 var properties = { physics: { speed: { x: "   X", y: "   Y", limit: { x: "    X,     .", y: "    Y,     ." } }, acceleration: { x: "    X.   ...", y: "    Y.   ..." }, }, live: { health: "  .", killing: "   .", level: "  ." } }; 

さらに、リストを使用すると、新しい種類のオブジェクトを簡単に作成できます。 これを行うには、異なるパラメーターでファクトリーを呼び出すだけです。 たとえば、車があり、そこからタンクを作る必要があります。 これを行うには、マシンの説明に、武器を担当するプロトタイプのパックを追加できます。 したがって、私たちは武器を持つ特定の車を取得しますが、実際には-戦車です。



工場が適切に機能していることを確認する方法は?

これを行うには、いくつかのオブジェクトを作成し、デバッガーを実行して、メモリ内のオブジェクトのプロパティとメソッドのアドレスを確認する必要があります。



インターフェイスの標準化



オブジェクトインターフェイスの標準化は、それらを操作するための共通モジュールの作成に役立ちます。 メソッドの本質は、それらの違いにもかかわらず、すべてのオブジェクトのAPIを特定の共通の標準形式にすることです。 次の例の方法を検討してください。

問題:

特定のゲームキャラクターと彼の周りの世界があります。 プレーヤーが「使用」ボタンを保持している場合、少なくとも2つの状況が考えられます。



解決策:



use()メソッドが武器と乗り物で異なることは明らかですが、工場でプロトタイプを継承するときにこれを実装する方法は?

答えはとても簡単です。 置換リストを作成し、メソッド名を割り当ててからリストにあるかどうかを確認する必要があります。 例:

 //       var replaceList = { ... weapon: "use", transport: "use" } //  ,      for(var method in prototypes) { var name = replaceList[method] || method; object.prototype[name] = prototype[method]; } 




したがって、武器がプロトタイプの「武器」とトランスポート-「トランスポート」を継承したという事実にもかかわらず、プロトタイプオブジェクトにはuseメソッドが1つしかなく、その背後で各オブジェクトは独自の機能をいくつか隠します。 そのような多型。

オブジェクトを保存およびロードする方法



たくさんの異なるオブジェクト(Minecraftを想像してください)のある大きな世界ができたので、次の問題を解決する時が来ました-セーブ/ロード関数の実装です。

一見、すべてがシンプルです。 なぜなら 多くのオブジェクトがあり、それらを文字列(JSON.stringify)に変換できますが、2つの問題があります。



各問題の解決策を個別に分析します。

オブジェクトの保管の禁止。



このメソッドの本質は、ファクトリでオブジェクトを作成した後、一意のIDが割り当てられ、オブジェクトの単一のレジスタに入れられることです。 単一のレジストリのタスクは、すべてのオブジェクトを格納し、要求に応じてIDで発行することです。 同時に、モジュールとサブシステムは、不必要な必要なくオブジェクトを使用することを禁じられており、それへのリンクを維持することは厳しく禁じられています。 システム全体はIDのみで動作し、特別な場合にのみオブジェクト自体を要求します。 例:

男がバスに乗る。 バスには多数の乗客がいます。 規則に従って、彼は乗客配列に入った人のIDを追加する必要があります。 人間のオブジェクト自体が配列に追加されると、過剰なネストが発生します。 これにより、神話的なメモリリークから始まり、ブート時にオブジェクト内のオブジェクトを反復処理する必要が生じるまで、多くの問題が発生します。 さらに、旅行中に何らかの理由でバスの乗客が死亡した場合、死体を取り除く必要があります。 乗客IDのみを保存すると、オブジェクトを抽出するときに、このIDを持つ乗客が存在しないことがわかり、次の反復に進みます。

または別の例:

マリオはコインを受け取ります。 実際、マリオはバックパックにIDコインのみを入れなければなりません。 彼がどこかでそれを使用する場合、システムはレジストリからコインオブジェクト自体を要求しますが、そのアクションを完了した後すぐにそれへのリンクを削除する必要があります。

このシステムは、レンダリング時のバグの回避にも役立ちます。 たとえば、何らかの理由で世界全体を削除し、カメラがオブジェクトを要求した場合。 レジストリがある場合、彼女はオブジェクトがもう存在しないことを理解し、このIDに関連付けられているすべての設定を削除します。

プロトタイプチェーンを復元します。



オブジェクトを文字列に変換すると、オブジェクトが所有していたプロトタイプが失われます。 したがって、変換する前に、prototype_listプロパティをオブジェクトに追加し、オブジェクトに含まれていたすべてのプロトタイプを一覧表示する必要があります(さらに、すべてのオブジェクトには、実際の塗りつぶしについて何も言わない標準インターフェイスがあるため、これもファクトリで行う必要があります) 。

さらに、読み込み作業中に、オブジェクトを工場に再度送信しますが、復元ワークショップでのみです。 そこでは、オブジェクトは作業の2番目の部分(リスト上のプロトタイプの継承)のみを渡します。

Source: https://habr.com/ru/post/J226261/


All Articles