从EventEmitter继承是一种反模式吗?

4

如果您希望您的类支持事件,从EventEmitter继承似乎是一种常见做法。例如,Google在Puppeteer中这样做,WebSocket模块这样做,mongoose也是如此,只是其中之一。

但这真的是好的做法吗?我的意思是,它看起来很好,很干净,但从面向对象编程的角度来看,它似乎是错误的。例如:

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

让它看起来好像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}
    }
}

现在你需要让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)
1个回答

4

没错,这绝对更有意义。

继承应该用于表示“是一个”关系:class Rectangle extends Shape 是可以的,因为 Rectangle 是一个形状(shape),但这里矩形本身并不是一个EventEmitter

相反,我们有一个“可以”关系:Rectangle “可以发射一个事件”,这正是你应该优先考虑组合而非继承的情况,这也是你最后一段代码片段中发生的情况。

我们只能推测为什么一些著名的库没有这样做 - 可能是为了向后兼容性、API 的简单性,或者仅仅是因为设计不良。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接