从 EventEmitter 继承是反模式吗?

For*_*vin 5 javascript anti-patterns node.js eventemitter es6-class

如果您希望您的类支持事件,那么从 EventEmitter 继承似乎是常见的做法。例如,Google 为Puppeteer执行此操作,WebSocket 模块执行此操作,mongoose 执行此操作,...仅举几例。

但这真的是好的做法吗?我的意思是它看起来确实漂亮干净,但从 OOP 的角度来看,这似乎是错误的。例如:

const EventEmitter = require('events')
class Rectangle extends EventEmitter {
    constructor(x,y,w,h) {
        super()
        this.position = {x:x, y:y}
        this.dimensions = {w:w, h:h}
    }
    setDimensions(w,h) {
        this.dimensions = {w:w, h:h}
        this.emit('dimensionsChanged')
    }
}
Run Code Online (Sandbox Code Playgroud)

看起来 Rectangle 的核心是一个 EventEmitter,尽管事件功能是次要的。

如果您决定Rectangle现在需要从一个名为 的新类继承怎么办Shape

class Shape {
    constructor(x,y) {
        this.position = {x:x, y:y}
    }
}

class Rectangle extends Shape {
    constructor(x,y,w,h) {
        super(x,y)
        this.dimensions = {w:w, h:h}
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您必须Shape从 EventEmitter 继承。即使只有一个继承自的类Shape实际上需要事件处理。

这样的事情不是更有意义吗?

class Shape {
    constructor(x,y) {
        this.position = {x, y}
    }
}

const EventEmitter = require('events')

class Rectangle extends Shape {
    constructor(x,y,w,h) {
        super(x,y)
        this.dimensions = {w, h}
        this.em = new EventEmitter()
    }
    setDimensions(w,h) {
        this.dimensions = {w:w, h:h}
        this.em.emit('dimensionsChanged')
    }
}

const rectangle = new Rectangle(1,2,3,4)
rectangle.em.on('dimensionsChanged', ()=>{console.log('dimensions changed')})
rectangle.setDimensions(10,20)
Run Code Online (Sandbox Code Playgroud)

Nin*_*liu 4

是的,这绝对更有意义。

应该使用继承来表示is关系:class Rectangle extends Shape可以,因为Rectangle是 形状,但这里矩形本身不是 EventEmitter

相反,我们有一个can关系:Rectangle 可以发出一个 event,而这正是您应该优先选择组合而不是继承的地方,这就是您最后一个代码片段中发生的情况。

我们只能推测为什么一些著名的库不这样做——复古兼容性、API 的简单性,或者仅仅是糟糕的设计。