【问题标题】:How to create a class which extends a not predetermined other class如何创建一个扩展未预先确定的其他类的类
【发布时间】:2019-02-01 15:30:35
【问题描述】:

我正在尝试创建一个应该能够扩展多个基类的类。在下面的示例中,我想使用石头或木头作为教室的基类。为此,我尝试创建一个可以选择适当基类的材料类。但我不让它工作。

const EventEmitter = require('events').EventEmitter; class Stone extends EventEmitter{ constructor(weight, color){ super(); this.color = color; this.weight = weight; this.hard = true this.start(); } testEmitterFunction(){ this.emit('test'); } start(){ setInterval(this.testFunc.bind(this), 500); } } class Wood{ constructor(weight, color){ this.color = color; this.weight = weight; this.hard = false; } } class Material{ constructor(mat, weight, color){ switch(mat){ case 'wood': return new Wood(weight, color); case 'stone': return new Stone(weight, color); } } } class House extends Material{ constructor(mat, weight, color, name){ super(mat, weight, color) this.name = name; this.on('test', (arg) => { console.log('1') }); this.test(); } test(){ console.log('test house function'); } } class House2 extends Stone{ constructor(weight, color, name){ super(weight, color) this.name = name; this.on('test', (arg) => { console.log('2') }); this.test(); } test(){ console.log('test house2 function'); } } const home = new House('stone', 8, 'green', 'homesweethome'); const home2 = new House2(8, 'green', 'homesweethome');

我希望该实例 home 具有与实例 home2 相同的行为。但在这种情况下,执行 console.log('test house function') 的测试功能不起作用。我尝试了其他解决方案,但 EventEmitter 不起作用或石头属性不可用。

【问题讨论】:

  • 也许你应该重新考虑你的结构并使用组合而不是如此深层次的继承。 Wood and Stone 可以扩展 Material,House 可以由材料和其他数据类型组成,即this.material = new Wood()。如果您对此感兴趣,我可以举个例子回答
  • 问题是,在我的现实生活场景中,我也希望能够为我的房子使用来自石头的 EventEmitter。即house.on(...) 而不是house.stone.on(...)
  • 使用合成并在您的房屋中创建一个事件发射器,然后将其传递给您的材质。
  • 真实案例而不是抽象可能会更好地了解您要实现的目标。为什么 Stone 不继承 EE 而 Wood 不继承?那个 Material 是一个外观看起来很可疑。最可疑的部分是 House 可能没有 on 方法,而它希望它存在。
  • @Lester 抱歉,我的意思是在House 中你可以on() {.....} 如果你还需要this 绑定到House 而不是Material 你可以on() { this.material.on.bind.apply(this, [null, ...arguments])); }

标签: javascript node.js oop ecmascript-6


【解决方案1】:

正如我在评论中提到的,使用composition over inheritance 可能会更好地完成您尝试做的事情。

作为一般的简化,如果您可以说“我的 X 是 Y 的一种类型”或“我的 X 是 Y”,这是有道理的,那就是继承,但如果您说“我的 X 是由 Y 组成”或“我的 X 包含 Y”,那么您应该使用组合。应用于您的案例,石头和木头都是一种材料。房子是一种材料吗?我不会这么说,但是房子是由石头或木头制成的,或者更确切地说,房子是由材料制成的,这意味着我们应该为此使用合成。

如果您想保留将字符串传递给设置材质的House 构造函数的能力,那么您仍然可以这样做。请参阅底部代码示例中的House#setMaterial,尽管factory pattern 将来可能更适合您。

你的结构的另一个问题是它杀死了polymorphism。如果您想要在 StoneWood 中执行相同操作的方法,例如“中断”,那么您必须复制粘贴相同的代码,但如果它们都继承自通用 Material 类型,那么您只需要在基类中创建一次方法。

我也希望能够在我的房子中使用来自石头的 EventEmitter。即 house.on(...) 而不是 house.stone.on(...)

在使用事件发射器时,我建议您在最高级别创建一个,然后将其传递给需要它的组件。在这种情况下,House 可以将事件发射器传递给材质或任何其他组件(例如房间)。由于 Javascript 的疯狂,House 可以成为事件发射器并将自己传递给材料。请参阅下面House 类中的House#setEmitter 函数。观察它在多态函数Material#Break中是如何使用的。

/** Unimportant */
class EventEmitter {
  constructor(){ this.map = {} }
  on(e, cb){
    if(!this.map[e]) this.map[e] = []
    this.map[e].push(cb)
  }
  emit(event,...data){
    if(!this.map[event]) return
    this.map[event].forEach(cb=>cb(...data))
  }
}
/**/

class Material {
  constructor(name = 'Something', weight = 5, color = 'black', hard = true){
    this.weight = weight
    this.color = color
    this.hard = hard
    this.name = name
  }
  setEmitter(emitter){
    this.emitter = emitter
  }
  
  break(){
    if(this.emitter){
      this.emitter.emit(`break`, `The ${this.name} has broken` )
    }
  }
  
  describe(){
    return `${this.weight}lb ${this.hard?'hard':'soft'} ${this.color} ${this.name}`
  }
}

class Stone extends Material {
  constructor(weight = 8, color = 'gray'){
    super("Stone", weight, color, true)
  }
}

class Wood extends Material {
  constructor(weight=4, color="brown"){
    super("Wood", weight, color, false)
  }
}

class House extends EventEmitter {
  constructor(material, name){
    super()
    this.material = this.setMaterial(material)
    this.name = name
    this.on('break', (what)=>{
      console.log(`${this.name} Event: `+what)
    })
  }
  
  setMaterial(mat){
    const matMap = {
      stone : Stone,
      wood : Wood
    }
    // Just gets a default material
    if(typeof mat == 'string'){
      mat = new matMap[mat]()
    }
    mat.setEmitter(this)
    return mat
  }
  // Logs information about the material
  describe(){
    console.log(`A house named ${this.name} made out of ${this.material.describe()}`)
  }
}


// Examples

// Method 1: Create a basic stone house and set material color later
const stoneHouse = new House("stone", "MyHouse")
stoneHouse.describe()
stoneHouse.material.color = "blue"
stoneHouse.describe()
stoneHouse.material.break()

// Method 2: Create a material and pass it to the house
const myMaterial = new Wood(6, "green")
const woodHouse = new House(myMaterial, "WoodHouse")
woodHouse.describe()
// Call a function that emits an event to the house
myMaterial.break()

【讨论】:

  • 非常感谢您的详尽回答。翻译后它适用于我的现实生活问题!我唯一需要调整的是,在我的问题中,Stone 想要排放一些东西到房子里。我更喜欢能够在石头代码中使用this.emit(...) 而不是this.emitter.emit(...)。有没有解决的办法?尽管如此,我从您的解决方案中学到了很多东西,它帮助我继续我的项目。虽然对于未来的问题,我不会像@estus 建议的那样抽象,所以帮助可以更准确:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-30
  • 1970-01-01
  • 2021-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多