Node-JS专精13_02继承和组合

继承和组合

现在给 Person 添加功能

  • person1.on(‘die’,fn)
  • person1.emit(‘die’,fn)
  • person1.off(‘die’,fn)
  • 让 Person 实例具有发布订阅功能,怎么做

可以加代码

1
2
3
4
5
6
7
8
9
10
11
class Person{
constructor(){}
sayHi()
cache = []
on(){}
off(){}
emit(){}
}

let p1 = new Person()
// 这样 p1 即是 人类 又能发布订阅

除了人还有另一个类,报社

1
2
3
4
5
6
7
8
9
10
11
class 报社{
constructor(){}
print()
cache = []
on(){}
off(){}
emit(){}
}

let 报社1 = new 报社()
// 这样 报社1 即是 报社 又能发布订阅

消除重复

Person 和报社有重复属性

  • 把重复属性提取出来,单独写个类 EventEmitter
  • 然后让 Person 和 报社 继承 EventEmitter

细节

  • constructor 要调用 super()
  • 来保证 EventEmitter 实例被初始化

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class EventEmitter{
constructor(){}
cache = []
on(){}
off(){}
emit(){}
}

class Person extends EventEmitter {
name
constructor(){ super() }
sayHi(){}
}

class aa extends EventEmitter{
name
constructor(){ super() }
print(){}
}

继承的问题

  • 如果我需要多个功能怎么办?

两个选择

  • 让 EventEmitter 继承其他类 (非常不好,类就不单纯了)
  • 让 Person 继承两个类(多继承,C++ 才支持 ,也不行)

组合

组合没有固定的写法

让 Person 实现发布订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person{
eventEmitter = new EventEmitter()
name
sayHi(){}
on(eventName,fn){
this.eventEmitter.on(eventName,fn)
}
off(eventName,fn){
this.eventEmitter.off(eventName,fn)
}
emit(eventName,data){
this.eventEmitter.emit(eventName,data)
}
}

优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person{
eventEmitter = new EventEmitter()
name
sayHi(){}
}

let p1 = new Person('aa')

mixin(p1,new EventEmitter())

function mixin(to,from){
for(let key in from){
to[key] = from[key]
}
}
// 注意这里是 简化版的 mixin, 实际会更复杂的

这样你就可以有更多的功能

1
2
3
4
5
6
7
8
9
class Person{
eventEmitter = new EventEmitter()
name
sayHi(){}
}
let p1 = new Person('aa')
mixin(p1,new EventEmitter())
mixin(p1,new Flyer())
mixin(p1,new Killer())

有了组合之后,你可能不需要class了

  • 直接函数 + 闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 看到如下类
class robot{
run()
}
class murderRobot extends robot{
kill()
}
class cleaningRobot extends robot{
clean()
}

class animal{
poop()
}
class dog extends animal{
wangwang()
}
class cat extends animal{
miaomiao()
}

需求 狗型杀人机器人 (变态的需求总是不期而遇)

继承很难做到,我们用组合

组合模式来看

  • dog = poop() + wangwang()
  • cat = poop() + miaomiao()
  • cleaningRobot = run() + clean()
  • murderRobot = run() + kill()
  • 狗型杀人机器人 = run() + kill() + wangwang()

不用 class写 dog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const createWang = (state) =>({
wangwang: ()=>{
console.log(`汪汪,我是${state.name}`)
}
})

const createRun = (state) =>({
run: ()=> state.position += 1
})

const createKill = (state) =>({
kill: ()=> {
console.log(`kill`)
}
})

const createDog = (name) =>{
const state = {name,position:0}
return Object.assign(
{},
createWang(state),
createRun(state),
createKill(state)
)
}

const dog = createDog('小白')

这就是组合模式

  • 缺点:写法太灵活,而且这是JS写法,如果是 Java 肯定不这样

结论和共识

  • 组合优于继承
    • 能实现 扁平化的重新分配
    • 而继承 是一个垂直方向的,如果你的需求是两个垂直线交叉 很难做!

使用场景

什么情况用继承

  • 开发者不会用组合
  • 功能单一,没太多交叉的地方,一眼能看出继承关系
  • 前端库比较喜欢用继承

举例

  • class App extends React.component{…}
  • React.Component 内置了方便的复用代码(钩子)

什么情况用组合

  • 功能组合非常灵活,无法一眼看出继承关系
  • 插件模式
  • mixin 模式

举例

  • Vue.mixin()
  • Vue.use(plugin)
  • Vue 的 install 功能
  • React接受组件的组件
  • const vm = new Vue({…})
    • vm.$on / vm.$off / vm.$emit
  • Vue 组合了 EventEmiter

什么情况用这些

  • 基础库用继承
  • 业务开发使用组合
  • 不一定是对的 灵活选择

组合的内存性能问题

  • 结论就是:实际上没多大区别

之前的 狗型杀人机器人

1
2
3
4
5
const dog = createDog('小白')
const d1 = createDog('a')
const d2 = createDog('b')

d1.run === d2.run // false

解决方案:通过一些技巧优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const f1 = ()=>{}
createF1 = (state)=>({f1:f1})
const f2 = ()=>{}
createF2 = (state)=>({f2:f2})

createDog = (name)=>{
const state = {name}
return Object.assign({},createF1(),createF2())
}

d1 = createDog(1)
d2 = createDog(2)

d1.f1 === d2.f1 // true

在看继承:实际也不省空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
sayA(){}
}

class B extends A{
name
constructor(name){
super()
this.name = name
}
sayB(){}
}

// 实际就是原型链
// 但是 构建B的过程 要先构建 A 多了一层原型 所以也并不是非常的省内存