今天我们模拟小米智能家居应用场景,融合单例模式、组合模式、观察者模式做一个综合应用案例。具体场景如下:
小米门铃为主人开门,触发开门事件;
小米智能控制台监测到开门事件,自动启动小米空调和小米电视;
思路分析
小米控制台,在家庭单位内只有一个实例,我们使用单例模式;
小米控制台通过startWork这一统一接口控制全部智能家居,对于有相同接口的不同实例进行统一调度,我们可以应用组合模式;
小米控制台监听小米门铃的开门事件,观察者模式;
定义【组合】和【组件】两个父类接口
后续由小米控制台对【组合】做具体实现,小米电视和小米空调对【组件】做具体实现
/* 组合模式 【组合】父类定义 */ class Compose { constructor (name){ this.name = name // components 组件调度列表 this.components = [] } // 添加组件到调度列表 addComponent(component){ this.components.push(component) } // 轮询所有组件 令其开始工作 startWork(){ this.components.forEach(component=>{ component.startWork() }) } } /* 组合模式 【组件】父类定义 */ class Component { // 接收组件名称 constructor(name){ this.name = name } // 所以组件统一调度接口 这里留白等待子类做具体实现 startWork(){} }
定义小米控制台类、小米电视类、小米空调类
小米控制台类,通过继承【组合】父类,实现了添加设备和指挥所有设备开始工作
/* 小米控制台 继承【组合】父类 实现添加组件、开始工作两个方法 */ class XiaomiControl extends Compose { constructor (){ // 调用父类方法为设备命名 super("小米总控台") } }
定义小米空调和小米电视类
继承【组件】父类,实现统一调度接口,为后续控制台对它们做统一调度做好准备
class XiaomiKongtiao extends Component { constructor(){ super("小米空调") } // 对统一调度接口做具体实现 startWork(){ console.log(`${this.name}开始调节室温`); } } class XiaomiTV extends Component { constructor(){ super("小米电视") } // 对统一调度接口做具体实现 startWork(){ console.log(`${this.name}自动切换到您喜欢的节目`); } }
设置小米控制台为全局唯一单例
好理解,一个家庭只需要一个控制台实例,通过getXmcSingleton()方法获取该实例
// instance 实例 singleton 单例 let xmcInstance = null function getXmcSingleton() { xmcInstance = xmcInstance === null? new XiaomiControl() : xmcInstance return xmcInstance }
实现统一调度
// 获取小米控制台实例 // const xmControl = new XiaomiControl() const xmControl = getXmcSingleton() // 创建小米电视和小米空调实例 const xmKongtiao = new XiaomiKongtiao() const xmTv = new XiaomiTV() // 添加电视和空调到调度列表 xmControl.addComponent(xmKongtiao) xmControl.addComponent(xmTv) // 所有智能家居开始工作 xmControl.startWork()
小米控制台控制所有智能设备开始工作的时机,为小米门铃开门的一刹那,所以接下来我们通过应用观察者模式实现这一需求;
定义被观察者(数据)和观察者两个父类
被观察者要实现注册观察者、注销观察者、改变数据状态、触发事件(所以观察者响应)四个接口
/* 提供被观察数据的父类接口 */ class Observable { constructor(){ // 预备存储事件类型和对应的观察者列表 this.typeListeners = {} // 预备存储事件类型和对应的值 this.typeValue = {} } // 注册观察者 addListener(type,...listeners){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ this.typeListeners[type].push(...listeners) }else{ this.typeListeners[type] = [...listeners] } } // 注销观察者 removeListener(type,listener){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ for(let i=this.typeListeners[type].length-1;i>=0;i--){ const item = this.typeListeners[type][i] if(item === listener){ this.typeListeners[type].splice(i,1) } } } } // 引起数据变化 setValue(type,newValue){ this.typeValue[type] = newValue // 数据变化触发事件 this.trigger(type,newValue) } // 触发观察者响应 trigger(type,newValue){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ this.typeListeners[type].forEach(item=>{ item.onEventHappen(type,newValue) }) } } }
观察者接口主要需要提供事件响应接口的定义
/* 观察者父类 */ class Observer { // 事件响应统一接口,留白待子类做具体实现 onEventHappen(type,newValue){} }
小米门铃实现【被观察者/数据】父类,通过继承获得注册设备、注销设备、改变数据和触发事件等具体功能; 在开关门时,会引起doorOpen事件的数据变化,从而触发观察者的响应动作
/* 小米门铃 实现被观察数据接口 开门关门就是事件 */ class XiaomiDoorbell extends Observable { // 开门时数据变化 父类帮你会【触发事件】(被观察者所提供的接口) open(){ this.setValue("doorOpen",true) } close(){ this.setValue("doorOpen",false) } }
小米控制台对观察者父类做具体实现,这里由于前面小米控制台已经继承过一遍【组合】这个父类,而在JavaScript中并不存在标准的多继承,所以这里我们只需要令其实现观察者的onEventHappen(type,value)接口,就能事实上令其成为一个具有观察者功能的类;
扩展后的小米控制台类代码如下:
/* 小米控制台 继承【组合】父类 实现添加组件、开始工作两个方法 */ class XiaomiControl extends Compose { constructor (){ // 调用父类方法为设备命名 super("小米总控台") } // 对【观察者】“父类”的事件响应接口做具体实现 // 成为一个“事实上”的观察者 onEventHappen(type,newValue){ switch (type) { case "doorOpen": if(newValue === true){ this.startWork() } break; default: break; } } }
OK,一切准备就绪,接下来创建门铃,添加小米控制台为观察者,并触发开门事件
// 创建小米门铃实例 const xmdb = new XiaomiDoorbell() // 添加小米控制台到doorOpen事件的观察者列表 xmdb.addListener("doorOpen",xmControl) // 通过按钮、定时器等触发小米门铃开门,进而触发小米控制台响应并令所有设备开始工作 // 这里简化一下 xmdb.open()
完整代码如下:
<script> /* 提供被观察数据的父类接口 */ class Observable { constructor(){ // 预备存储事件类型和对应的观察者列表 this.typeListeners = {} // 预备存储事件类型和对应的值 this.typeValue = {} } // 注册观察者 addListener(type,...listeners){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ this.typeListeners[type].push(...listeners) }else{ this.typeListeners[type] = [...listeners] } } // 注销观察者 removeListener(type,listener){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ for(let i=this.typeListeners[type].length-1;i>=0;i--){ const item = this.typeListeners[type][i] if(item === listener){ this.typeListeners[type].splice(i,1) } } } } // 引起数据变化 setValue(type,newValue){ this.typeValue[type] = newValue // 数据变化触发事件 this.trigger(type,newValue) } // 触发观察者响应 trigger(type,newValue){ if(Object.prototype.hasOwnProperty.call(this.typeListeners,type)){ this.typeListeners[type].forEach(item=>{ item.onEventHappen(type,newValue) }) } } } /* 观察者父类 */ class Observer { // 事件响应统一接口,留白待子类做具体实现 onEventHappen(type,newValue){} } /* 组合模式 【组合】父类定义 */ class Compose { constructor (name){ this.name = name // components 组件调度列表 this.components = [] } // 添加组件到调度列表 addComponent(component){ this.components.push(component) } // 轮询所有组件 令其开始工作 startWork(){ this.components.forEach(component=>{ component.startWork() }) } } /* 组合模式 【组件】父类定义 */ class Component { // 接收组件名称 constructor(name){ this.name = name } // 所以组件统一调度接口 这里留白等待子类做具体实现 startWork(){} } // instance 实例 singleton 单例 let xmcInstance = null function getXmcSingleton() { xmcInstance = xmcInstance === null? new XiaomiControl() : xmcInstance return xmcInstance } /* 小米门铃 实现被观察数据接口 开门关门就是事件 */ class XiaomiDoorbell extends Observable { // 开门时数据变化 父类帮你会【触发事件】(被观察者所提供的接口) open(){ this.setValue("doorOpen",true) } close(){ this.setValue("doorOpen",false) } } /* 小米控制台 继承【组合】父类 实现添加组件、开始工作两个方法 */ class XiaomiControl extends Compose { constructor (){ // 调用父类方法为设备命名 super("小米总控台") } // 对【观察者】“父类”的事件响应接口做具体实现 // 成为一个“事实上”的观察者 onEventHappen(type,newValue){ switch (type) { case "doorOpen": if(newValue === true){ this.startWork() } break; default: break; } } } class XiaomiKongtiao extends Component { constructor(){ super("小米空调") } // 对统一调度接口做具体实现 startWork(){ console.log(`${this.name}开始调节室温`); } } class XiaomiTV extends Component { constructor(){ super("小米电视") } // 对统一调度接口做具体实现 startWork(){ console.log(`${this.name}自动切换到您喜欢的节目`); } } </script>
原文:https://juejin.cn/post/7096185381820825613