43

TL;DR:

プロトタイプの OO にファクトリ/コンストラクタが必要ですか? パラダイム スイッチを作成して、それらを完全にドロップできますか?

バックストーリー:

私は最近、JavaScript でプロトタイプの OO をいじっていましたが、JavaScript で行われた OO の 99% は、古典的な OO パターンをそれに強制していることに気付きました。

典型的な OO に対する私の見解は、それには 2 つのことが関係しているということです。メソッド (および静的データ) の静的プロトタイプとデータ バインディング。ファクトリやコンストラクタは必要ありません。

JavaScript では、これらは関数と を含むオブジェクト リテラルObject.createです。

これは、すべてを静的な設計図/プロトタイプとしてモデル化し、できればドキュメント スタイルのデータベースに直接フックされるデータ バインディングの抽象化を行えることを意味します。つまり、オブジェクトはデータベースから取り出され、プロトタイプをデータで複製することによって作成されます。これは、コンストラクターロジック、ファクトリー、no がないことを意味しnewます。

サンプルコード:

疑似例は次のとおりです。

var Entity = Object.create(EventEmitter, {
    addComponent: {
        value: function _addComponent(component) {
            if (this[component.type] !== undefined) {
                this.removeComponent(this[component.type]);
            }

            _.each(_.functions(component), (function _bind(f) {
                component[f] = component[f].bind(this);
            }).bind(this));

            component.bindEvents();

            Object.defineProperty(this, component.type, {
                value: component,
                configurable: true
            });

            this.emit("component:add", this, component);
        }
    },
    removeComponent: {
        value: function _removeComponent(component) {
            component = component.type || component;

            delete this[component];

            this.emit("component:remove", this, component);
        }
    }
}

var entity = Object.create(Entity, toProperties(jsonStore.get(id)))

マイナーな説明:

ES5 は冗長であるため、特定のコードは冗長です。Entity上記は設計図/プロトタイプです。データを持つ実際のオブジェクトは、 を使用して作成されObject.create(Entity, {...})ます。

実際のデータ (この場合はコンポーネント) は、JSON ストアから直接読み込まれ、呼び出しに直接挿入されObject.createます。もちろん、コンポーネントの作成にも同様のパターンが適用され、合格したプロパティのみObject.hasOwnPropertyがデータベースに保存されます。

エンティティが初めて作成されるとき、それは空で作成されます{}

実際の質問:

今、私の実際の質問は

  • JSプロトタイプOOのオープンソースの例?
  • これは良い考えですか?
  • 原型的な OOP の背後にあるアイデアや概念と一致していますか?
  • コンストラクター/ファクトリー関数を使用しても、どこかでお尻を噛むことはありませんか? コンストラクタを使わなくても本当にうまくいくでしょうか。上記の方法論を使用して、それらを克服するために工場が必要な制限はありますか?
4

4 に答える 4

26

オブジェクト指向プログラミングについての考え方を変える限り、コンストラクター/ファクトリー ロジックはまったく必要ないと思います。このトピックを最近調べたところ、プロトタイプの継承が、特定のデータを使用する一連の関数を定義するのにより適していることがわかりました。これは、古典的な継承の訓練を受けた人にとっては異質な概念ではありませんが、問題は、これらの「親」オブジェクトが操作対象のデータを定義していないことです。

var animal = {
    walk: function()
    {
        var i = 0,
            s = '';
        for (; i < this.legs; i++)
        {
            s += 'step ';
        }

        console.log(s);
    },
    speak: function()
    {
        console.log(this.favoriteWord);
    }
}

var myLion = Object.create(animal);
myLion.legs = 4;
myLion.favoriteWord = 'woof';

したがって、上記の例では、動物に付随する機能を作成し、その機能を持つオブジェクトと、アクションを完了するために必要なデータを作成します。これは、古典的な継承に長い間慣れている人にとっては、不快で奇妙に感じます。パブリック/プライベート/保護されたメンバーの可視性のヒエラルキーの暖かいあいまいさはまったくありません。私はそれが私を緊張させることを最初に認めます.

また、上記のオブジェクトの初期化を見たときの私の最初の本能は、myLion動物のファクトリーを作成することです。そのため、簡単な関数呼び出しでライオン、トラ、クマを作成できます。そして、それはほとんどのプログラマーにとって自然な考え方だと思います。上記のコードの冗長さは見苦しく、洗練されていないように見えます。それが単に古典的なトレーニングによるものなのか、それとも上記の方法の実際の誤りなのかはまだわかりません.

さて、継承へ。

JavaScript の継承が難しいことは常に理解しています。プロトタイプチェーンの内外をナビゲートすることは、正確には明確ではありません. で使用するまでObject.createは、式からすべての関数ベースの新しいキーワードのリダイレクトが取り除かれます。

animal上記のオブジェクトを拡張して、人間を作りたいとしましょう。

var human = Object.create(animal)
human.think = function()
{
    console.log('Hmmmm...');
}

var myHuman = Object.create(human);
myHuman.legs = 2;
myHuman.favoriteWord = 'Hello';

これにより、プロトタイプとして持つオブジェクトが作成humanされ、次にプロトタイプとして持つオブジェクトが作成されanimalます。簡単です。誤った指示、「関数のプロトタイプと等しいプロトタイプを持つ新しいオブジェクト」はありません。単純なプロトタイプの継承です。シンプルでわかりやすいです。ポリモーフィズムも簡単です。

human.speak = function()
{
    console.log(this.favoriteWord + ', dudes');
}

プロトタイプ チェーンの動作方法により、myHuman.speakは で発見されるhuman前に で発見されるanimalため、私たちの人間は単なる退屈な古い動物ではなく、サーファーです。

したがって、結論として(TLDR):

疑似古典的なコンストラクター機能は、古典的な OOP で訓練されたプログラマーをより快適にするために JavaScript に追加されました。決して必要ではありませんが、メンバーの可視性や (トートロジー的に) コンストラクターなどの古典的な概念を放棄することを意味します。

その見返りとして得られるのは、柔軟性とシンプルさです。その場で「クラス」を作成できます。すべてのオブジェクトは、それ自体が他のオブジェクトのテンプレートです。子オブジェクトに値を設定しても、それらのオブジェクトのプロトタイプには影響しません (つまり、 を使用var child = Object.create(myHuman)してから set を設定child.walk = 'not yet'してanimal.walkも影響はありません。実際にテストしてください)。

継承の単純さは、正直言って気が遠くなるようなものです。私は JavaScript の継承について多くのことを読み、それを理解するために多くのコード行を書きました。しかし、実際にはオブジェクトが他のオブジェクトから継承するということになります。それはそれと同じくらい簡単で、newキーワードはそれを混乱させるだけです。

この柔軟性を最大限に活用するのは困難であり、私はまだそれを行う必要があると確信していますが、そこにあり、ナビゲートするのは興味深いものです. それが大規模なプロジェクトで使用されなかった理由のほとんどは、単純に理解されていないことが原因だと思います。 C++、Java などを教えられました。

編集

私はコンストラクターに対してかなり良い主張をしたと思います。しかし、工場に対する私の主張はあいまいです。

さらに熟考し、その間にフェンスの両側に何度かフリップフロップした結果、工場も不要であるという結論に達しました。animal(上記) に別の function が与えられた場合、initializeから継承する新しいオブジェクトを作成して初期化するのは簡単animalです。

var myDog = Object.create(animal);
myDog.initialize(4, 'Meow');

初期化され、使用できる状態になった新しいオブジェクト。

@Raynos - あなたはこれで私を完全に狙撃しました。何も生産的なことをしないで 5 日間過ごす準備をしなければなりません。

于 2011-06-29T21:17:13.633 に答える
11

あなたのコメントによると、質問は主に「コンストラクターの知識は必要ですか?」です。だと感じます。

おもちゃの例は、部分的なデータの保存です。メモリ内の特定のデータセットでは、永続化するときに特定の要素のみを保存することを選択できます (効率性のため、またはデータの一貫性のために、たとえば値は永続化されると本質的に役に立たなくなります)。ユーザー名とユーザーがヘルプ ボタンをクリックした回数を保存するセッションを見てみましょう (より良い例がないため)。私の例でこれを永続化すると、クリック数は使用できなくなります。これは、今はメモリに保持しているためです。次にデータをロードするとき (次にユーザーがログインまたは接続するときなど) を初期化しますゼロからの値 (おそらく 0)。この特定のユース ケースは、コンストラクター ロジックの適切な候補です。

ああ、でもいつでもそれを静的プロトタイプに埋め込むことができます。Object.create({name:'Bob', clicks:0});もちろん、この場合は。しかし、値が最初は常に 0 ではなく、計算が必要な場合はどうなるでしょうか。うーん、たとえば、ユーザーは秒単位で年齢を重ねます (名前と生年月日が保存されていると仮定します)。繰り返しになりますが、とにかく取得時に再計算する必要があるため、ほとんど使用されないアイテムが持続します。では、ユーザーの年齢を静的プロトタイプに格納するにはどうすればよいでしょうか?

明らかな答えは、コンストラクター/イニシャライザーのロジックです。

他にも多くのシナリオがありますが、そのアイデアが js oop や特定の言語にあまり関係しているとは思いません。エンティティ作成ロジックの必要性は、コンピューター システムが世界をモデル化する方法に固有のものです。格納するアイテムは、プロトタイプ シェルのような設計図への単純な取得と挿入である場合もあれば、値が動的であり、初期化が必要な場合もあります。

アップデート

では、より現実的な例を試してみましょう。混乱を避けるために、データベースはなく、データを永続化する必要もないと仮定します。私がソリティアサーバーを作っているとしましょう。それぞれの新しいゲームは (当然のことながら)Gameプロトタイプの新しいインスタンスになります。ここで必要なイニシャライザロジック(およびその多く)であることは明らかです。

たとえば、各ゲーム インスタンスで、静的/ハードコードされたカード デッキだけでなく、ランダムにシャッフルされたデッキが必要になります。静的な場合、ユーザーは毎回同じゲームをプレイすることになり、明らかに良くありません。

プレーヤーがなくなった場合、ゲームを終了するためにタイマーを開始する必要がある場合もあります。繰り返しますが、私のゲームにはいくつかの要件があるため、静的にできるものではありません。秒数は、接続されたプレーヤーがこれまでに勝ったゲームの数に反比例します (繰り返しますが、保存された情報はありません。この接続で何回かだけです)。 、およびシャッフルの難易度に比例します(シャッフルの結果に応じてゲームの難易度を決定できるアルゴリズムがあります)。

static でどのようにしますObject.create()か?

于 2011-06-29T20:20:04.670 に答える
2

静的に複製可能な「タイプ」の例:

var MyType = {
  size: Sizes.large,
  color: Colors.blue,
  decay: function _decay() { size = Sizes.medium },
  embiggen: function _embiggen() { size = Sizes.xlarge },
  normal: function _normal() { size = Sizes.normal },
  load: function _load( dbObject ) { 
    size = dbObject.size
    color = dbObject.color 
  }
}

さて、このタイプを別の場所に複製できますか? もちろん、 を使用する必要がありvar myType = Object.Create(MyType)ますが、それで終わりですよね? 今、私たちはちょうどmyType.sizeそれが事のサイズです。または、色を読み取ったり、変更したりすることもできます。コンストラクターなどは作成していませんよね?

そこにコンストラクターがないと言った場合、あなたは間違っています。コンストラクターの場所を示しましょう。

// The following var definition is the constructor
var MyType = {
  size: Sizes.large,
  color: Colors.blue,
  decay: function _decay() { size = Sizes.medium },
  embiggen: function _embiggen() { size = Sizes.xlarge },
  normal: function _normal() { size = Sizes.normal },
  load: function _load( dbObject ) { 
    size = dbObject.size
    color = dbObject.color 
  }
}

必要なものはすべて作成し、すべてを定義したからです。コンストラクタが行うのはそれだけです。したがって、静的なものを複製/使用するだけでも (上記のスニペットがそうしているように見えます)、まだコンストラクターがあります。ただの静的コンストラクタ。型を定義することで、コンストラクターを定義しました。別の方法は、次のオブジェクト構築モデルです。

var MyType = {}
MyType.size = Sizes.large

しかし、最終的には Object.Create(MyType) を使用することになり、その場合は、静的オブジェクトを使用してターゲット オブジェクトを作成したことになります。そして、前の例と同じになります。

于 2011-06-29T21:59:02.080 に答える