JavaScript中的工厂方法、构造函数与class

JavaScript中的工厂方法、构造函数与class

classclass

首先,让我们看看这三种方式的例子:

// class
class ClassCar {
drive () {

console.log(&#39;Vroom!&#39;);<br/>

}
} const car1 = new ClassCar();
console.log(car1.drive()); // constructor(构造函数模式)
function ConstructorCar () {} ConstructorCar.prototype.drive = function () {
console.log(‘Vroom!’);
}; const car2 = new ConstructorCar();
console.log(car2.drive()); // factory (工厂模式)
const proto = {
drive () {

console.log(&#39;Vroom!&#39;);<br/>

}
}; function factoryCar () {
return Object.create(proto);
} const car3 = factoryCar();
console.log(car3.drive());

这些方式都将方法存储于共享的原型中,然后通过构造函数的闭包有选择的支持私有数据。换句话说,他们几乎拥有相同的特性,所以通常来说也能交替使用。

class
class
class Foo {}
console.log(typeof Foo); // function
class
classthismyFoo = new Foo()class
class
new
new
function Foo() {
if (!(this instanceof Foo)) { return new Foo(); }
}
newnewnew 
new

构造函数的调用方法与构造函数的实现方式紧密耦合。当你需要其具有工厂方法的灵活性时,重构起来将会是巨大的变化。将class放入工厂方法进行重构是非常常见的,它甚至被写入了Martin Fowler, Kent Beck, John Brant, William Opdyke, 和 Don Roberts的 “Refactoring: Improving the Design of Existing Code” 。

3. 构造函数模式违背了开/闭原则

new

我的意见是,既然从类到工厂方法的重构是非常常见的,那么应该将不应该造成任何破坏作为所有构造函数进行扩展时的标准。

如果你开放了一个构造函数或者类,而用户使用了这个构造函数,在这之后,如果需要增加这个方法的灵活性,(例如,换成使用对象池的方式进行实现,或者跨执行上下文的实例化,或者使用替代原型来拥有更多的继承灵活性),都需要用户同时进行重构。

不幸的是,在JavaScript中,从构造函数或者类切换到工厂方法需要进行巨大的改变

// Original Implementation:
// 原始实现: // class Car {
// drive () {
// console.log(‘Vroom!’);
// }
// } // const AutoMaker = { Car }; // Factory refactored implementation:
// 工厂函数重构实现:
const AutoMaker = {
Car (bundle) {

return Object.create(this.bundle[bundle]);<br/>

}, bundle: {

premium: {<br/>
  drive () {<br/>
    console.log(&#39;Vrooom!&#39;);<br/>
  },<br/>
  getOptions: function () {<br/>
    return [&#39;leather&#39;, &#39;wood&#39;, &#39;pearl&#39;];<br/>
  }<br/>
}<br/>

}
}; // The refactored factory expects:
// 重构后的方法希望这样调用
const newCar = AutoMaker.Car(‘premium’);
newCar.drive(); // ‘Vrooom!’ // But since it‘s a library, lots of callers
// in the wild are still doing this:
// 但是由于这是一个库,许多用户依然这样使用
const oldCar = new AutoMaker.Car(); // Which of course throws:
// TypeError: Cannot read property ’undefined‘ of
// undefined at new AutoMaker.Car
// 这样的话,就会抛出:
// TypeError: Cannot read property ’undefined‘ of
// undefined at new AutoMaker.Car

在上面的例子中,我们首先提供了一个类,但是接下来希望提供不同的汽车种类。于是,工厂方法为不同的汽车种类使用了不同的原型。我曾经使用这种技术来存储不同的播放器接口,然后通过要处理的文件格式选择合适的原型。

instanceof
instanceofinstanceofinstanceof
instanceof
// instanceof is a prototype identity check.
// NOT a type check. // instanceof是一个原型检查
// 而不是类型检查 // That means it lies across execution contexts,
// when prototypes are dynamically reassigned,
// and when you throw confusing cases like this
// at it: function foo() {}
const bar = { a: ’a‘}; foo.prototype = bar; // Is bar an instance of foo? Nope!
console.log(bar instanceof foo); // false // Ok… since bar is not an instance of foo,
// baz should definitely not be an instance of foo, right?
const baz = Object.create(bar); // …Wrong.
console.log(baz instanceof foo); // true. oops.
instanceof[[Prototype]]Constructor.prototype
instanceofConstructor.prototypeinstanceof
Constructor.prototypethisConstructor.prototype
instanceof

使用class的优点

  • 方便的,自包含的语法。
  • 是JavaScript中使用类的一种单一、规范的方式。在ES6之前,在一些流行的库中已经出现了其实现方式。
  • 对于有基于类的语言的背景的人来说,会更加熟悉。

使用class的缺点

除了具有构造函数的缺点外,还有:

  • 用户可能会尝试使用extends关键字来创建导致问题的多层级的类。

多层级的类将会导致许多在面向对象程序设计中广为人知的问题,包括脆弱的基类问题,香蕉猴子雨林问题,必要性重复问题等等。不幸的是,class可以用来extends就像球可以用来扔,椅子可以用来坐一样自然。想了解更多内容,请阅读“The Two Pillars of JavaScript: Prototypal OO” and “Inside the Dev Team Death Spiral”.

extends

&gt; 功能可见性是让你能够执行一定动作的机会。例如,旋钮可以用来旋转,杠杆可以用来拉,按钮可以用来按,等等。

使用工厂方法的优点

extends

1. 返回任意对象与使用任意原型

例如,你可以通过同一API轻松的创建多种类型的对象,例如,一个能够针对不同类型视频实例化播放器的媒体播放器,活着能够出发DOM事件或web socket事件的事件库。

工厂函数还能跨越之行上下文来实例化对象,充分利用对象池,并且允许更灵活的原型模型继承。

2. 没有重构的忧虑

你永远不需要从一个工厂转换到一个构造函数,所以重构将永远不会是一个问题。

new
newthis
this
thisplayer.create()thiscall()apply()this
instanceof
myFoo = createFoo()

工厂方法的缺点

Factory.prototypeinstanceofinstanceofthis

结论

classnew

你也许会想可以只重构调用部分,不过在大的团队中,或者你使用的class是公共API的一部分,你就有可能要破坏不在你掌控中的代码。换句话说,不能假设只重构调用部分永远是一个可选项。

关于工厂方法,有趣的事情在于它们不仅更加强大和灵活,而且是鼓励整个团队,以及所有API用户使用简单、灵活和安全的模式的最简单的方法。

For more training in in prototypal inheritance techniques, factory functions, and object composition, be sure to check out “The Two Pillars of JS: Composition with Prototypes” — free for members. Not a member yet?

Eric Elliott_ is the author of (O’Reilly), and . He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean,Metallica, and many more._

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.