web-dev-qa-db-ja.com

カイルシンプソンのOLOOパターンとプロトタイプデザインパターン

カイルシンプソンの「OLOO(他のオブジェクトにリンクするオブジェクト)パターン」は、プロトタイプデザインパターンとどのように異なりますか? 「リンク」(プロトタイプの動作)を具体的に示すものでそれを作り出し、ここで発生する「コピー」(クラスの動作)がないことを明確にする以外に、彼のパターンは正確に何を導入しますか?

カイルのパターンの例 彼の本「JSを知らない:this&Object Prototypes」から:

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create(Foo);

Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
};

var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");

b1.speak(); // alerts: "Hello, I am b1."
b2.speak(); // alerts: "Hello, I am b2."
101
shmuli

彼のパターンは正確に何を紹介していますか?

OLOOは、リンクを取得するために他の(IMOを混乱させる)セマンティクスに重ねる必要なく、プロトタイプチェーンをそのまま採用します。

したがって、これらの2つのスニペットはまったく同じ結果になりますが、異なる方法で到達します。

コンストラクター形式:

function Foo() {}
Foo.prototype.y = 11;

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;

var x = new Bar();
x.y + x.z;  // 42

OLOOフォーム:

var FooObj = { y: 11 };

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
x.y + x.z;  // 42

両方のスニペットで、xオブジェクトは[[Prototype]]-にリンクされ、オブジェクト(Bar.prototypeまたはBarObj)にリンクされ、3番目のオブジェクト(Foo.prototypeまたはFooObj)。

スニペット間の関係と委任は同一です。メモリ使用量はスニペット間で同一です。多くの「子」(別名、x1からx1000などの多くのオブジェクト)を作成する機能は、スニペット間で同じです。委任のパフォーマンス(x.yおよびx.z)は、スニペット間で同一です。オブジェクト作成のパフォーマンスis OLOOでは遅くなりますが、 健全性チェック は、パフォーマンスの低下が実際に問題ではないことを示しています。

OLOOが提供するものは、コンストラクタ/ newメカニズムを介して間接的にリンクするよりも、オブジェクトを単に表現して直接リンクする方がはるかに簡単だということです。後者はクラスに関するふりをしますが、実際には委任を表現するためのひどい構文です(サイドノート:ES6 class構文も!) 。

OLOOは中間者を切り取っているだけです。

別の比較 of class vs OLOOです。

141
Kyle Simpson

私はカイルの本を読みましたが、特にthisがどのようにバインドされているかについての詳細は、非常に有益であることがわかりました。

長所:

私にとっては、OLOOの大きな利点がいくつかあります。

1.シンプルさ

OLOOはObject.create()に依存して、別のオブジェクトに[[prototype]]リンクされた新しいオブジェクトを作成します。関数がprototypeプロパティを持っていることを理解したり、その変更に起因する潜在的な関連する落とし穴を心配する必要はありません。

2.よりクリーンな構文

これは議論の余地がありますが、OLOO構文は(多くの場合)より洗練され、「標準」のjavascriptアプローチよりも簡潔で、特にポリモーフィズム(super- style呼び出し)に関してはそう感じています。

短所:

疑わしい設計が1つあると思います(実際に上記の2に貢献するもの)。それはシャドーイングに関するものです。

振る舞いの委任では、[[Prototype]]チェーンの異なるレベルで考えられるすべてのものに同じ名前を付けることを避けます。

この背後にある考え方は、オブジェクトが独自のより具体的な関数を持ち、それが内部的にチェーンの下位の関数に委任されるというものです。たとえば、オブジェクトのJSONバージョンをサーバーに送信するsave()関数を持つresourceオブジェクトがありますが、stripAndSave()関数を持つclientResourceオブジェクトもあります。最初に、サーバーに送信すべきではないプロパティを削除します。

潜在的な問題は、誰かがやって来て、プロトタイプチェーン全体を完全に認識せずにspecialResourceオブジェクトを作成することに決めた場合、ベースをシャドウするsaveというプロパティの下に最後の保存のタイムスタンプを保存することを合理的に決定するかもしれませんresourceオブジェクトのsave()機能は、プロトタイプチェーンの2つのリンクにあります。

var resource = {
  save: function () { 
    console.log('Saving');
  }
};

var clientResource = Object.create(resource);

clientResource.stripAndSave = function () {
  // Do something else, then delegate
  console.log('Stripping unwanted properties');
  this.save();
};

var specialResource = Object.create( clientResource );

specialResource.timeStampedSave = function () {
  // Set the timestamp of the last save
  this.save = Date.now();
  this.stripAndSave();
};

a = Object.create(clientResource);
b = Object.create(specialResource);

a.stripAndSave();    // "Stripping unwanted properties" & "Saving".
b.timeStampedSave(); // Error!

これは特に不自然な例ですが、ポイントは具体的にはnot他のプロパティをシャドウイングすると、いくつかの厄介な状況とシソーラスの大量使用につながる可能性があるということです!

おそらく、これのより良い例はinitメソッドでしょう。特にOOLOサイドステップコンストラクター型関数としては痛烈です。関連するすべてのオブジェクトにこのような関数が必要になる可能性が高いため、それらに適切な名前を付けるのは退屈な作業になる可能性があります。

*実際には特に合理的ではありません(lastSavedの方がはるかに優れていますが、これは単なる例です)。

24
Ed Hinchliffe

「JSを知らない:this&Object Prototypes」での議論とOLOOのプレゼンテーションは考えさせるものであり、私はこの本を通してたくさんのことを学びました。 OLOOパターンのメリットは、他の回答で詳しく説明されています。ただし、以下のペットに関する苦情があります(または、効果的に適用できないようなものがありません)。

1

「クラス」が古典的なパターンで別の「クラス」を「継承」する場合、2つの関数は同様の構文( 「関数宣言」または「関数ステートメント」 )として宣言できます。

function Point(x,y) {
    this.x = x;
    this.y = y;
};

function Point3D(x,y,z) {
    Point.call(this, x,y);
    this.z = z;
};

Point3D.prototype = Object.create(Point.prototype);

対照的に、OLOOパターンでは、ベースおよび派生オブジェクトを定義するために使用されるさまざまな構文形式:

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    }
};


var Point3D = Object.create(Point);
Point3D.init = function(x,y,z) {
    Point.init.call(this, x, y);
    this.z = z;
};

上記の例でわかるように、ベースオブジェクトはオブジェクトリテラル表記を使用して定義できますが、派生オブジェクトに同じ表記を使用することはできません。この非対称性は私を悩ませます。

2

OLOOパターンでは、オブジェクトの作成は2つのステップです。

  1. Object.createを呼び出します
  2. オブジェクトを初期化するために、いくつかのカスタムの非標準メソッドを呼び出します(オブジェクトごとに異なる場合があるため、覚えておく必要があります)。

     var p2a = Object.create(Point);
    
     p2a.init(1,1);
    

対照的に、Prototypeパターンでは、標準演算子newを使用します。

var p2a = new Point(1,1);

3

古典的なパターンでは、(.prototypeとは対照的に) "クラス"関数に直接割り当てることにより、 "インスタンス"に直接適用しない "静的な"ユーティリティ関数を作成できます。例えば。以下のコードの関数squareのように:

Point.square = function(x) {return x*x;};

Point.prototype.length = function() {
    return Math.sqrt(Point.square(this.x)+Point.square(this.y));
};

対照的に、OLOOパターンでは、オブジェクトインスタンスでも([[プロトタイプ]]チェーンを介して)すべての「静的」関数を使用できます。

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    },
    square: function(x) {return x*x;},
    length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));}
};
13

「各オブジェクトを他のオブジェクトに依存させるように考えました」

Kyleが2つのオブジェクトが[[Prototype]]リンクされていると説明するように、それらは互いに依存していません。代わりに、それらは個別のオブジェクトです。 [[Prototype]]リンケージを使用して、あるオブジェクトを別のオブジェクトにリンクしているので、いつでも変更できます。 OLOOスタイルで作成された2つの[[Prototype]]リンクオブジェクトを互いに依存している場合、constructor呼び出しで作成されたオブジェクトについても同様に考える必要があります。

var foo= {},
    bar= Object.create(foo),
    baz= Object.create(bar);


console.log(Object.getPrototypeOf(foo)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //foo

console.log(Object.getPrototypeOf(baz)) //bar

では、foobarbazがお互いに依存していると思いますか?

では、これと同じconstructorスタイルのコードをやってみましょう。

function Foo() {}

function Bar() {}

function Baz() {}

Bar.prototype= Object.create(Foo);
Baz.prototype= Object.create(Bar);

var foo= new Foo(),
    bar= new Bar().
    baz= new Baz();

console.log(Object.getPrototypeOf(foo)) //Foo.prototype
console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //Bar.prototype
console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype

console.log(Object.getPrototypeOf(baz)) //Baz.prototype
console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype

後者と前者のコードの唯一の違いは、後者のfoobarbaz bbjectsは、constructor関数の任意のオブジェクトを介して互いにリンクされていることです(Foo.prototypeBar.prototypeBaz.prototype)しかし、前者(OLOOスタイル)では直接リンクされます。両方の方法で、単にfoobarbazを前者では直接、後者では間接的にリンクしています。ただし、どちらの場合も、オブジェクトは相互に独立しています。なぜなら、インスタンス化されたクラスは、他のクラスから継承させることができないクラスのインスタンスのようではないからです。オブジェクトが委任するオブジェクトもいつでも変更できます。

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);

したがって、それらはすべて互いに独立しています。

OLOOが、各オブジェクトが他のオブジェクトについて何も知らないという問題を解決することを望んでいました。」

はい、それは確かに可能です-

Techをユーティリティオブジェクトとして使用しましょう。

 var Tech= {
     tag: "technology",
     setName= function(name) {
              this.name= name;
}
}

Tech-にリンクするオブジェクトをいくつでも作成します

var html= Object.create(Tech),
     css= Object.create(Tech),
     js= Object.create(Tech);

Some checking (avoiding console.log)- 

    html.isPrototypeOf(css); //false
    html.isPrototypeOf(js); //false

    css.isPrototypeOf(html); //false
    css.isPrototypeOf(js); //false

    js.isPrototypeOf(html); //false
    js.isPrototypwOf(css); //false

    Tech.isPrototypeOf(html); //true
    Tech.isPrototypeOf(css); //true
    Tech.isPrototypeOf(js); //true

htmlcssjsオブジェクトは互いに接続されていると思いますか?いいえ、そうではありません。それでは、constructor関数を使用してどのように処理できたかを見てみましょう。

function Tech() { }

Tech.prototype.tag= "technology";

Tech.prototype.setName=  function(name) {
              this.name= name;
}

Tech.proptotype-にリンクするオブジェクトをいくつでも作成します

var html= new Tech(),
     css= new Tech(),
      js= new Tech();

いくつかのチェック(console.logの回避)-

html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false

css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false

js.isPrototypeOf(html); //false
js.isPrototypeOf(css); //false

Tech.prototype.isPrototypeOf(html); //true
Tech.prototype.isPrototypeOf(css); //true
Tech.prototype.isPrototypeOf(js); //true

これらのconstructorスタイルのオブジェクト(htmlcssjs)オブジェクトはOLOOスタイルのコードとどう違うと思いますか?実際、それらは同じ目的を果たします。 OLOOスタイルの1つのオブジェクトはTechに委任します(委任は明示的に設定されました)が、constructorスタイルの1つのオブジェクトはTech.prototypeに委任します(委任は暗黙的に設定されました)。最終的には、OLOO- styleを直接使用し、constructor- styleを間接的に使用して、互いにリンクされていない3つのオブジェクトを1つのオブジェクトにリンクします。

「ObjBは、ObjAから作成する必要があります。Object.create(ObjB)など」

いいえ、ここのObjBは、クラスObjAのインスタンス(従来の言語では)とは異なります。 作成時にobjBオブジェクトをObjAオブジェクトにデリゲートするように言う必要があります。コンストラクタを使用した場合、間接的に.prototypes。

5
Abhishek Sachan

@Marcus @bholben

おそらく私たちはこのようなことをすることができます。

    const Point = {

        statics(m) { if (this !== Point) { throw Error(m); }},

        create (x, y) {
            this.statics();
            var P = Object.create(Point);
            P.init(x, y);
            return P;
        },

        init(x=0, y=0) {
            this.x = x;
            this.y = y;
        }
    };


    const Point3D = {

        __proto__: Point,

        statics(m) { if (this !== Point3D) { throw Error(m); }},

        create (x, y, z) {
            this.statics();
            var P = Object.create(Point3D);
            P.init(x, y, z);
            return P;
        },

        init (x=0, y=0, z=0) {
            super.init(x, y);
            this.z = z;
        }
    }; 

もちろん、Point2DオブジェクトのプロトタイプにリンクするPoint3Dオブジェクトを作成するのはちょっと馬鹿げていますが、それはポイントの横にあります(私はあなたの例と一致したかったです)。とにかく、苦情に関する限り:

  1. 非対称性は、ES6の Object.setPrototypeOf で修正するか、使用している__proto__ = ...にもっと眉をひそめます。 Point3D.init()に見られるように、通常のオブジェクトでも super を使用することもできます。別の方法は、次のようなことをすることです

    const Point3D = Object.assign(Object.create(Point), {  
        ...  
    }   
    

    構文は特に好きではありませんが。


  1. いつでもp = Object.create(Point)をラップしてからp.init()をコンストラクターにラップできます。例えばPoint.create(x,y)。上記のコードを使用して、次の方法でPoint3D "インスタンス"を作成できます。

    var b = Point3D.create(1,2,3);
    console.log(b);                         // { x:1, y:2, z:3 }
    console.log(Point.isPrototypeOf(b));    // true
    console.log(Point3D.isPrototypeOf(b))   // true
    

  1. OLOOで静的メソッドをエミュレートするために、このハックを思いつきました。私はそれが好きかどうかわからない。 「静的」メソッドの先頭で特別なプロパティを呼び出す必要があります。たとえば、Point.create()メソッドを静的にしました。

        var p = Point.create(1,2);
        var q = p.create(4,1);          // Error!  
    

または、ES6 Symbols を使用すると、JavaScriptベースクラスを安全に拡張できます。そのため、コードを保存して、Object.prototypeの特別なプロパティを定義できます。例えば、

    const extendedJS = {};  

    ( function(extension) {

        const statics = Symbol('static');

        Object.defineProperty(Object.prototype, statics, {
            writable: true,
            enumerable: false,
            configurable: true,
            value(obj, message) {
                if (this !== obj)
                    throw Error(message);
            }
        });

        Object.assign(extension, {statics});

    })(extendedJS);


    const Point = {
        create (x, y) {
            this[extendedJS.statics](Point);
            ...

3
Andrew Szymczak

@james emanon-それで、あなたは多重継承について言及しています(「JS:this&Object Prototypesを知らない」という本の75ページで説明しています)。そして、そのメカニズムは、たとえばアンダースコアの「拡張」機能で見つけることができます。あなたの例で述べたオブジェクトの名前は、リンゴ、オレンジ、キャンディーを少し混ぜたものですが、背後にあるポイントを理解しています。私の経験から、これはOOLOバージョンになります:

var ObjA = {
  setA: function(a) {
    this.a = a;
  },
  outputA: function() {
    console.log("Invoking outputA - A: ", this.a);
  }
};

// 'ObjB' links/delegates to 'ObjA'
var ObjB = Object.create( ObjA );

ObjB.setB = function(b) {
   this.b = b;
}

ObjB.setA_B = function(a, b) {
    this.setA( a ); // This is obvious. 'setA' is not found in 'ObjB' so by prototype chain it's found in 'ObjA'
    this.setB( b );
    console.log("Invoking setA_B - A: ", this.a, " B: ", this.b);
};

// 'ObjC' links/delegates to 'ObjB'
var ObjC = Object.create( ObjB );

ObjC.setC = function(c) {
    this.c = c;  
};

ObjC.setA_C = function(a, c) {
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA
    this.setC( c );
    console.log("Invoking setA_C - A: ", this.a, " C: ", this.c);
};

ObjC.setA_B_C = function(a, b, c){
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA
    this.setB( b );
    this.setC( c );
    console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c);
};

ObjA.setA("A1");
ObjA.outputA(); // Invoking outputA - A:  A1

ObjB.setA_B("A2", "B1"); // Invoking setA_B - A:  A2  B:  B1

ObjC.setA_C("A3", "C1"); // Invoking setA_C - A:  A3  C:  C1
ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A:  A4  B:  B2  C:  C1

簡単な例ですが、示されている点は、オブジェクトをかなりフラットな構造/フォーメーションでつなぎ合わせているだけで、複数のオブジェクトのメソッドとプロパティを使用できる可能性があることです。クラス/「プロパティのコピー」アプローチと同じことを達成します。カイルによる集計(114ページ、「this&Object Prototypes」):

つまり、実際のメカニズム、つまりJavaScriptで活用できる機能にとって重要なのは、他のオブジェクトにリンクされているオブジェクトについてです。

あなたにとってより自然な方法は、チェーン全体をモデル化するのではなく、すべての「親」(注意:))オブジェクトを1つの場所/関数呼び出しで述べることであることを理解しています。

それに必要なのは、それに応じてアプリケーションの問題を考え、モデル化することです。私もそれに慣れています。それが助けて、カイル自身からの最終評決が素晴らしいことを願っています。 :)

2
NenadPavlov