Kyle Simpson的OLOO模式与原型设计模式

时间:2015-04-22 05:05:50

标签: javascript design-patterns

Kyle Simpson的“OLOO(链接到其他对象的对象)模式”与原型设计模式有什么不同?除了通过特别指示“链接”(原型的行为)的东西来创造它并澄清这里没有“复制”(类的行为),他的模式究竟引入了什么?

这是他的书中的an example of Kyle's pattern,“你不懂JS:这个和对象的原型”:

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."

8 个答案:

答案 0 :(得分:140)

  

他的模式到底引入了什么?

OLOO按原样包含原型链,无需在其他(IMO混淆)语义上进行分层以获得链接。

因此,这两个片段具有完全相同的结果,但以不同的方式实现。

构造函数表单:

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.prototypeBarObj),而该对象又链接到第三个对象({{ 1}}或Foo.prototype)。

片段之间的关系和委派是相同的。片段之间的内存使用情况相同。创建许多“子”(也就是FooObjx1之类的许多对象等)的能力在片段之间是相同的。代理(x1000x.y)的性能在代码段之间是相同的。 OLOO的对象创建性能 较慢,但sanity checking that表明性能较慢并不是问题。

我认为OLOO提供的是,表达对象并直接链接它们要比通过构造函数/ x.z机制间接链接它们简单得多。后者假装是关于类的,但实际上只是表达委派的一种可怕的语法(旁注: ES6 new语法也是如此!)。

OLOO正在切断中间人。

这是class与OLOO的another comparison

答案 1 :(得分:24)

我读过Kyle的书,我发现它确实很有用,特别是有关this如何约束的详细信息。

优点:

对我来说,有一些OLOO的大职业选手:

1。简单

OLOO依靠Object.create()创建一个新对象[[prototype]] - 链接到另一个对象。您不必理解函数具有prototype属性或担心其修改可能带来的任何潜在缺陷。

2。更清晰的语法

这是有争议的,但我觉得OLOO语法(在很多情况下)比“标准”更简洁,更简洁。 javascript方法,特别是涉及多态(super - 样式调用)。

缺点:

我认为有一个有问题的设计(实际上有助于上面的第2点),这与阴影有关:

  

在行为授权中,我们避免在[[Prototype]]链的不同级别上尽可能地命名相同的东西。

这背后的想法是对象有自己更具体的功能,然后内部委托给链下游的功能。例如,您可能有一个resource对象,其上有一个save()函数,该对象将该对象的JSON版本发送到服务器,但您可能还有一个clientResource对象,该对象具有stripAndSave()函数,它首先删除不应发送到服务器的属性。

潜在的问题是:如果其他人出现并决定制作一个specialResource对象,而不是完全了解整个原型链,他们可能会合理地*决定为属性下的最后一次保存保存时间戳名为save,它隐藏了save()对象上的基本resource功能,在原型链中有两个链接:

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!

这是一个特别人为的例子,但问题是,特别是阴影其他属性可能导致一些尴尬的情况和大量使用同义词库!

或许更好地说明这个问题的方法是init方法 - 尤其是OOLO会回避构造函数类型函数。由于每个相关对象都可能需要这样的功能,因此对它们进行适当的命名可能是一项繁琐的工作,而且唯一性可能使得难以记住使用哪个。

*实际上它并不是特别合理(lastSaved会好得多,但它只是一个例子。)

答案 2 :(得分:13)

“你不了解JS:这个和对象原型”中的讨论和OLOO的演示是发人深省的,我已经学到了很多东西。 OLOO模式的优点在其他答案中有详细描述;但是,我有以下宠物抱怨(或者我错过了阻止我有效应用它的东西):

<强> 1

当经典模式中的“类”“继承”另一个“类”时,可以将这两个函数声明为类似语法("function declaration" or "function statement"):

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模式中,创建对象有两个步骤:

  1. 致电Object.create
  2. 调用一些自定义的非标准方法来初始化对象(你必须记住它,因为它可能因对象而异):

     var p2a = Object.create(Point);
    
     p2a.init(1,1);
    
  3. 相反,在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模式中,对象实例上也可以使用任何“静态”函数(通过[[prototype]]链):

    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));}
    };
    

答案 3 :(得分:5)

  

&#34;我想这样做会使每个obj依赖于另一个&#34;

当Kyle解释两个对象[[Prototype]]链接时,它们并非如此 相互依赖;相反,它们是个体对象。你正在链接一个 使用[[Prototype]]链接对象,您可以随时更改。 如果您将通过OLOO样式创建的两个[[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

现在想一想,您认为foo barbaz依赖于彼此吗?

现在让我们做同样的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个对象通过任意对象相互链接 他们的constructor函数(Foo.prototypeBar.prototypeBaz.prototype),但在前者(OLOO样式)中,它们是直接链接的。这两种方式都只是将foobarbaz彼此直接关联,直接在前者中,间接在后者中。但是,在这两种情况下,对象都是彼此独立的,因为它不像任何曾经实例化的类的实例,不能从其他类继承。您始终可以更改对象应该委托的对象。

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

所以他们都是彼此独立的。

  

&#34;我希望OLOO可以解决每个对象都知道的问题   没有关于另一个。&#34;

是的,确实可能 -

让我们使用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 - 样式中,一个对象委托给Tech(明确设置了委托),而在constructor - 样式中,一个对象委托给Tech.prototype(委托是隐式设置的)。最终,您最终将三个对象(彼此之间没有链接)链接到一个对象,直接使用OLOO - 样式,间接使用constructor - 样式。

  

&#34;原样,ObjB必须从ObjA创建.. Object.create(ObjB)等&#34;

不,ObjB这里不像任何类的实例(基于经典语言) ObjA可以说像objB对象被委托给ObjA对象创建它 时间&#34; 即可。如果您使用了构造函数,那么您可以使用.prototype来间接地执行相同的耦合操作。

答案 4 :(得分:3)

@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__ = ...越皱眉头了。我们现在也可以在常规对象上使用super,如Point3D.init()中所示。另一种方法是做一些像

    这样的事情
    const Point3D = Object.assign(Object.create(Point), {  
        ...  
    }   
    
    但是我并不特别喜欢语法。

    1. 我们总是可以将p = Object.create(Point)然后p.init()包装到构造函数中。例如Point.create(x,y)。使用上面的代码,我们可以创建一个Point3D&#34;实例&#34;以下列方式。

      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. 我想出了这个hack来模拟OLOO中的静态方法。我不确定我是否喜欢它。它需要在任何&#34;静态&#34;的顶部调用一个特殊属性。方法。例如,我已将Point.create()方法设为静态。

            var p = Point.create(1,2);
            var q = p.create(4,1);          // Error!  
        
      2. 或者,使用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);
                    ...
        

答案 5 :(得分:2)

@james emanon - 所以,你指的是多重继承(在第37页的书中讨论过#34;你不知道JS:这个&对象原型&#34;)。我们可以在下划线中找到这种机制&#34;扩展&#34;例如,功能。您在示例中说明的对象名称有点混合苹果,橙子和糖果,但我理解背后的观点。根据我的经验,这将是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

这是一个简单的例子,但显示的是我们只是将对象链接在一起,形成相当扁平的结构/形式,并且仍然可以使用来自多个对象的方法和属性。我们实现与类/&#34相同的东西;复制属性&#34;做法。 Kyle总结(第114页,&#34;此&amp; Object Prototypes&#34;):

  换句话说,实际的机制,重要的是什么的本质   我们可以在JavaScript中使用的功能是所有关于对象的功能   被链接到其他对象

我明白,更自然的方式是陈述所有的父母&#34; (小心:))对象在一个地方/函数调用而不是建模整个链。

它需要的是根据这一点在我们的应用程序中改变思维和建模问题。我也习惯了。希望它有所帮助,凯尔本人的最终判决将是伟大的。 :)

答案 6 :(得分:-1)

@Marcus,就像你一样,我一直热衷于OLOO,也不喜欢你的第一点所描述的不对称性。我一直在玩抽象,以恢复对称性。您可以创建一个link()函数来代替Object.create()。使用时,您的代码可能看起来像这样......

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


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

请记住,Object.create()有第二个可以传入的参数。这是利用第二个参数的链接函数。它还允许一些自定义配置...

function link(delegate, props, propsConfig) {
  props = props || {};
  propsConfig = propsConfig || {};

  var obj = {};
  Object.keys(props).forEach(function (key) {
    obj[key] = {
      value: props[key],
      enumerable: propsConfig.isEnumerable || true,
      writable: propsConfig.isWritable || true,
      configurable: propsConfig.isConfigurable || true
    };
  });

  return Object.create(delegate, obj);
}

当然,我认为@Kyle不支持在Point3D对象中隐藏init()函数。 ; - )

答案 7 :(得分:-1)

有没有办法让OLOO超过“两个”对象..所有的例子我都是基于基础的例子(参见OP的例子)。假设我们有以下对象,我们如何创建一个具有“其他”三个属性的“第四”对象? ALA ...

var Button = {
     init: function(name, cost) {
       this.buttonName = name;
       this.buttonCost = cost;
     }
}

var Shoe = {
     speed: 100
}

var Bike = {
     range: '4 miles'
}

这些对象是任意的,可以包含各种行为。但要点是,我们有'n'个对象,而我们的新对象需要三个方面的东西。

而不是给定的例子ala:

var newObj = Object.create(oneSingularObject);
    newObj.whatever..

但是,我们的newObject =(按钮,自行车,鞋子......)

在OLOO中实现这一目标的模式是什么?