在实例构建器中放置实例属性,好还是坏?

时间:2014-11-04 17:19:09

标签: javascript inheritance design-patterns prototype

我刚刚阅读了this关于JavaScript中原型继承的非常好的文章,但很惊讶地看到作者对原型中定义属性的强烈反对。

  

为来自经典OOP的程序员创建原型链的对象时常见的错误是在链中高高地定义公共属性,因为它们存在于所有实例中。我们觉得需要定义属性,就好像抽象对象描述了一个接口。然而,在原型中定义将存在于从其中下降的对象中的属性是没有意义的。 Javascript与Java不同:您不会在基础对象中声明与后代的所有实例不同的变量。您仅在将定义变量的级别上声明变量。

     

取我们动物的名字属性。由于每个动物都有一个名字,所以很自然地认为这个属性对所有动物都是通用的,并在动物原型的共同标准中定义它。问题是,Animal没有名字。 Dog实例有一个名称。

     

在Javascript中,你不能说动物有一个名字。动物是一个对象,而不是定义,即使我们这样使用它。而且该对象没有名称属性。如果动物没有名字,为什么动物的方法中会提到名称呢?因为Animal是抽象的:它本身并不打算使用。在Animal中,永远不会引用Animal。它将引用来自Animal,dino的任何对象。而迪诺有一个名字。

如果我有一组非常复杂的类,它们有许多共同的属性。我不知道如果在基类中完成一次工作,复制那些属性以及在每个可实例化的派生类上设置它们的工作会更好,即使该基类是“抽象”的

例如:

function Analysis(args){
  args = args || {};
  // Extract supported init properties from args
  this.description = args.description;
  this.host = args.host;
  this.source = args.source;
  this.identifier = args.identifier;
  this.vendor = args.vendor;
  this.agent = args.agent;
  //etc...
}

function PortfolioAnalysis(args){
  Analysis.call(this, args);
  args = args || {};
  this.portfolio = args.portfolio;
  this.author = args.author;
  //etc...
}
PortfolioAnalysis.prototype = Object.create(Analysis.prototype);
PortfolioAnalysis.prototype.constructor = PortfolioAnalysis;

function TreatyAnalysis(args){
  Analysis.call(this, args);
  args = args || {};
  this.treaty = args.treaty;
  this.terms = args.terms;
  //etc...
}
TreatyAnalysis.prototype = Object.create(Analysis.prototype);
TreatyAnalysis.prototype.constructor = TreatyAnalysis;

//etc...

所以文章说我应该在每个派生类中粘贴属性descriptionhostsource等的初始化代码,并将其从基础中删除类。

我不明白为什么会更好,特别是如果使用这些共享属性构建这些对象时有一堆复杂的常见逻辑,那么在基类中定义它们有什么不好,如果它太糟糕了,是否存在围绕它的方式,不涉及代码重复或必须定义一个单独的'.init()'方法?

2 个答案:

答案 0 :(得分:1)

  

所以文章说我应该在每个派生类中粘贴属性描述,主机,源等的初始化代码,并将其从基类中删除。

没有。你的代码完全没问题,应该如何完成。

该文章说,descriptionhost等属性应放在实例上(如new ThreatAnalysis(…),甚至new Analysis(…)),但< em {not on Analysis.prototype - 正是你在做什么。有些人会“默认”,例如在identifier上为空,Analysis.prototype等,因为他们想要“声明”每个Analysis实例都应该有一个标识符。正如文章所解释的那样,这就是垃圾。

Analysis构造函数中共享您的初始化行为很好(正如文章所提到的,共享函数可以放在原型链中)。没有必要内联它并使Analysis和空对象,即使它是抽象的,永远不会直接实例化。

答案 1 :(得分:0)

我认为你还没有获得原型继承。作者并不是说“不要将初始化代码放在基础构造函数中”。作者所说的是“不要在基础原型上放置属性”。这完全不同。

所以你被允许做你目前正在做的事情。这完全没问题。然而,您不应该做的是将属性的默认值放在原型上,因为它可能会导致问题。例如,考虑:

function Foo() {}

Foo.prototype.data = []; // you shouldn't do this

var a = new Foo;
var b = new Foo;

a.data.push(0);

alert(JSON.stringify(b.data)); // [0]

这就是为什么你不应该在原型上共享属性。我们修改了a.data的值,但由于dataFoo的所有实例之间共享,我们还修改了b.data。因此违反了不变量。

这样想:

  1. 当前对象上定义的属性是其公共属性。它们不是共享的。
  2. 在当前对象的原型上定义的属性是静态属性。它们在原型的实例中共享。
  3. 因此,在原型上定义静态属性(如所有实例的count)是可以的。但是,在原型上定义公共属性是不行的,因为它可能会导致类似上面的问题。

    博士。 Alex Rauschmayer更好地解释了这一点:http://www.2ality.com/2013/09/data-in-prototypes.html

    您的代码很好,因为构造函数中的this始终指向当前对象,而不是原型。因此,您没有在原型上定义任何属性。

    我认为你因为构造函数和原型而感到困惑。也许这篇博客文章会澄清你的怀疑:http://aaditmshah.github.io/why-prototypal-inheritance-matters/