我正在尝试创建一个应该能够扩展几个基类的类。在下面的示例中,我想使用石材或木材作为教室的基类。为此,我尝试创建一个可以选择适当基类的材料类。但是我不起作用。
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无法正常工作,或者无法使用stone属性。
答案 0 :(得分:2)
正如我在评论中提到的那样,使用composition over inheritance可能会更好地完成您要尝试执行的操作。
就像你可以说一般的简化“我X是Y型的”或“我X是Y”,它是有道理的,这是继承的,但如果你说“我X是由的Y”或“我的X包含Y”,那么您应该使用合成。应用于您的案件,石头和木头都是材料的一种。是房子的材料类型的?我不会这样说,但房子的是由的石头或木头,或者说的,房子是材料制成的,这意味着我们应该使用成分为。
如果您想保持将字符串传递给设置材料的House
构造函数的能力,那么您仍然可以这样做。请参阅底部的代码示例中的House#setMaterial
,尽管将来factory pattern可能会更适合您。
您的结构的另一个问题是它会杀死polymorphism。如果你想在做同样的事情的方法都Stone
和Wood
,说“破”,那么你就必须在复制粘贴相同的代码,但如果他们都从一个普通的继承材料类型,那么只需要创建方法曾经在的基类。
我也希望能够将石头的EventEmitter用于我的房屋。即house.on(...)代替house.stone.on(...)
在使用事件发射器时,建议您在尽可能高的级别上创建一个,然后将其传递给需要它的组件。在这种情况下,House可以将事件发射器传递给材料或任何其他组件(例如房间)。由于Javascript的疯狂性,House可以成为事件发射器并将其自身传递给素材。请参见下面的House#setEmitter
类中的House
函数。该观察它是如何在多态函数使用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()