Parece ser una práctica común heredar de EventEmitter si desea que su clase admita eventos. Por ejemplo, Google lo hace para Puppeteer , el módulo WebSocket lo hace, mongoose lo hace , ... solo por nombrar algunos.
Pero, ¿es esta realmente una buena práctica? Quiero decir, seguro que se ve bien y limpio, pero desde la perspectiva de la programación orientada a objetos parece incorrecto. Por ejemplo:
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') } }
haría que pareciera que Rectangle es un EventEmitter en su núcleo, aunque la funcionalidad de eventos es secundaria.
¿Qué sucede si decide que Rectangle
ahora necesita heredar de una nueva clase llamada 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} } }
Ahora tendría que hacer que Shape
herede del EventEmitter. Incluso si solo una clase que hereda de Shape
realmente necesita eventos.
¿No tendría mucho más sentido algo como esto?
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)
Sí, absolutamente tiene más sentido.
La herencia debe usarse para indicar una relación: class Rectangle extends Shape
está bien, ya que Rectangle
es una forma, pero aquí el rectángulo en sí no es un EventEmitter
.
En su lugar, tenemos una relación can : Rectangle
puede emitir un evento , y ahí es exactamente donde debería favorecer la composición sobre la herencia , que es lo que sucede en su último fragmento de código.
Solo podemos especular sobre por qué algunas librerías famosas no hacen eso: retrocompatibilidad, simplicidad de las API o simplemente mal diseño.