这是我参与「第四届青训营 」笔记创作活动的的第9天,总结前端中常见的设计模式。
设计模式 overview
每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。 —— Christopher Alexander
SOLID设计原则
"SOLID" 是由罗伯特·C·马丁在 21 世纪早期引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则。
- S – Single Responsibility Principle 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
- O – OpenClosed Principle 开放/封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
- L – Liskov Substitution Principle 里氏替换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
- I – Interface Segregation Principle 接口隔离原则
- 保持接口的单一独立
- 类似单一职责原则,这里更关注接口
- D – Dependency Inversion Principle 依赖倒转原则
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
工厂模式
在 JavaScript 中,我们使用构造函数去初始化对象,就是应用了构造器模式。
构造器就是将一些属性赋值给对象的过程封装,确保每个对象都具有这些属性,保证共性不变,同时可以对这些属性的取值操作开放,保证了个性的灵活。
简单工厂
function User(name , age, career, work) {
this.name = name
this.age = age
this.career = career
this.work = work
}
function Factory(name, age, career) {
let work
switch(career) {
case 'coder':
work = ['写代码','写系分', '修Bug']
break
case 'product manager':
work = ['订会议室', '写PRD', '催更']
break
case 'boss':
work = ['喝茶', '看报', '见客户']
case 'xxx':
// 其它工种的职责分配
...
return new User(name, age, career, work)
}
把这个承载了共性的 User 类和个性化的逻辑判断写入同一个函数:这样对于有新添加的不同职位就直接改 Factory 而不用去修改 User 类了。
什么是工厂模式?:工厂模式其实就是将创建对象的过程单独封装。
工厂模式的目的: 无脑传参
抽象工厂
对于上面的简单工厂的示例存在这样的问题,如果每一次去在 Factory 中添加,就会导致 Factory 变得会异常庞大。主要原因是我们没有遵守开放封闭原则。
开放封闭原则:对拓展开放,对修改封闭。说得更准确点,软件实体(类、模块、函数)可以扩展,但是不可修改。所以,我们并没有真正在做添加,而是一直疯狂地修改。
通常抽象工厂一共包括四个部分:抽象工厂,具体工厂,抽象产品,具体产品。
抽象说明并不确定具体要实现的功能,只知道一定会包括这些方法,并且这些方法必须进行重载。
class MobilePhoneFactory {
// 提供操作系统的接口
createOS(){
throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
}
// 提供硬件的接口
createHardWare(){
throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
}
}
抽象工厂不干活,具体工厂(ConcreteFactory)来干活!
// 具体工厂继承自抽象工厂
class FakeStarFactory extends MobilePhoneFactory {
createOS() {
// 提供安卓系统实例
return new AndroidOS()
}
createHardWare() {
// 提供高通硬件实例
return new QualcommHardWare()
}
}
因此不管我们后续需要如何修改都不用改变 MobilePhoneFactory,需要新建继承 MobilePhoneFactory就可以了。
因此我们可以用一个抽象产品(AbstractProduct)类来声明这一类产品应该具有的基本功能。
// 定义操作系统这类产品的抽象产品类
class OS {
controlHardWare() {
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
controlHardWare() {
console.log('我会用安卓的方式去操作硬件')
}
}
class AppleOS extends OS {
controlHardWare() {
console.log('我会用🍎的方式去操作硬件')
}
}
// 定义手机硬件这类产品的抽象产品类
class HardWare {
// 手机硬件的共性方法,这里提取了“根据命令运转”这个共性
operateByOrder() {
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
// 定义具体硬件的具体产品类
class QualcommHardWare extends HardWare {
operateByOrder() {
console.log('我会用高通的方式去运转')
}
}
class MiWare extends HardWare {
operateByOrder() {
console.log('我会用小米的方式去运转')
}
}
...
因此,如果我们需要生产一台手机就可以如下操作
// 这是我的手机
const myPhone = new FakeStarFactory()
// 让它拥有操作系统
const myOS = myPhone.createOS()
// 让它拥有硬件
const myHardWare = myPhone.createHardWare()
// 启动操作系统(输出‘我会用安卓的方式去操作硬件’)
myOS.controlHardWare()
// 唤醒硬件(输出‘我会用高通的方式去运转’)
myHardWare.operateByOrder()
单例模式
单例的意思是一个类只有一个实例,并且提供一个访问它的全局访问点。
要实现单例模式,那么就要求构造函数具备判断自己是否已经创建过一个实例的能力。
静态方法实现
class SingleDog {
show() {
console.log('我是一个单例对象')
}
static getInstance() {
// 判断是否已经new过1个实例
if (!SingleDog.instance) {
// 若这个唯一的实例不存在,那么先创建它
SingleDog.instance = new SingleDog()
}
// 如果这个唯一的实例已经存在,则直接返回
return SingleDog.instance
}
}
const s1 = SingleDog.getInstance()
const s2 = SingleDog.getInstance()
// true
s1 === s2
闭包实现
SingleDog.getInstance = (function() {
// 定义自由变量instance,模拟私有变量
let instance = null
return function() {
// 判断自由变量是否为null
if(!instance) {
// 如果为null则new出唯一实例
instance = new SingleDog()
}
return instance
}
})()
原型模式
原型模式不仅是一种设计模式,它还是一种编程范式(programming paradigm),是 JavaScript 面向对象系统实现的根基。
观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
- 被观察者拥有所有观察者的完整数组
- 事件发布时便利这个完整的列表通知每一个观察者
class Student{
constructor(name){
this.name = name;
}
notice(msg){
console.log(`我${this.name},接收到了这条消息`);
}
}
class Teacher{
constructor(){
this.observer = []; // 记录所有的观察者
}
addObeserver(ob){
this.observer.push(ob);
return this;
}
sendMsg(msg){
this.observer.forEach(ele=>ele.notice(msg));
}
}
// 使用
const MissLiu = new Teacher();
const Zhangsan = new Student('zhangSan');
const LiSi = new Student('LiSi');
const WangWu = new Student('WangWu');
MissLiu.addObeserver(Zhangsan).addObeserver(LiSi).addObeserver(WangWu);
MissLiu.sendMsg('歪比歪比');
发布订阅者模式
- 订阅者订阅对应事件
- 事件发布时所有对应消息订阅回调全部执行
class Manager{
// 所有消息的消息池
messages = {}
// 订阅函数
on(msgName, callbck){
if(this.messages[msgName]){
// 如果已经有人订阅
this.messages[msgName].push(callbck);
console.log(this.messages)
}else{
// 如果暂时没有
this.messages[msgName] = [callbck];
console.log(this.messages)
}
}
// 发布函数
emit(msgName,...param){
if(this.messages[msgName]){
this.messages[msgName]
.forEach(callback => callback.call(this,...param))
}
}
}
// 使用
const manager = new Manager();
manager.on('click',(msg)=>{
console.log(`event1:接收到事件${msg}`);
})
manager.on('click',(msg)=>{
console.log(`event2:接收到事件${msg}`);
})
manager.on('click',(msg)=>{
console.log(`event3:接收到事件${msg}`);
})
setTimeout(()=>{
manager.emit('click');
},2000)