Как я уже упоминал в своем комментарии, то, что вы пытаетесь сделать, может быть лучше сделано с использованием композиция над наследованием .
Как общее упрощение, если вы можете сказать: «Мой X - это тип Y» или «Мой X - это Y», и это имеет смысл, это наследство, но если вы скажете: «Мой X состоит из Y"или" Мой X содержит Y ", тогда вы должны использовать композицию.Применительно к вашему случаю, Камень и Дерево являются типом Материала.Дом - это тип материала?Я бы так не сказал, но дом сделан из камня или дерева, точнее, дом сделан из материала, а это значит, что мы должны использовать для этого композицию.
Если выЕсли вы хотите оставить возможность передавать строку конструктору House
, который устанавливает материал, тогда вы все равно можете это сделать.См. House#setMaterial
в примере кода внизу, хотя фабричный шаблон может работать лучше для вас в будущем.
Другая проблема с вашей структурой состоит в том, что она убивает полиморфизм .Если вам нужны методы, которые делают то же самое в Stone
и Wood
, скажем, «взлом», вам придется скопировать и вставить один и тот же код, но если они оба унаследованы от общего типа Material, товам нужно создать метод только один раз в базовом классе.
Я хочу иметь возможность использовать EventEmitter из камня и для моего дома.т.е. house.on (...) вместо house.stone.on (...)
Когда дело доходит до использования Eventemitter, я бы порекомендовал вам создать его на максимально возможном уровнеи затем передайте это компонентам, которые нуждаются в этом.В этом случае Хаус может передать источник событий в материал или любые другие компоненты (например, комнату).Из-за сумасшествия Javascript, Хаус может быть и даже передавать себя материалу.См. Функцию 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()