这是我参与「第四届青训营 」笔记创作活动的第6天
一、什么是设计模式
1.定义:
软件设计中常见问题的解决方案模型
- 历史经验的总结
- 与特定语言无关
2.背景:
“设计模式”一词最早出现在1977年出版的一本建筑领域的书中
3.设计模式分类
《设计模式:可复用面向对象软件的基础》中提到23种设计模式并划分了三个类型:
- 创建型——如何创建一个对象
- 结构型——如何灵活的将对象组装成较大的结构
- 行为型——负责对象间的高效通信和职责划分
二、前端场景下的设计模式
1.浏览器(api)中的设计模式
- 单列模式
- 发布订阅模式
⑴单列模式
定义:全局唯一访问对象(一个类仅有一个实例)。
简单理解:在任何地方访问的都是同一个对象,如果在任意一个地方对该对象进行修改,其它用到该对象的地方也会同步这次修改的结果。
例:
浏览器当中的window对象——对浏览器当中所有的操作进行了封装。
应用场景
- 缓存
- 全局状态管理
用单例模式实现请求缓存
使用场景:在不同的时间/地方发送相同的url请求,希望在第二次发送请求时可以复用之前的值(缓存),以便更好的用户体验
实现代码
typescript语法实现:
import {api} from "./utils" //定义一个mock的api,每500ms返回一个值
export class Requset {
static instance: Requset;//用来存储全局唯一实例
private cache: Record<string, string>//用来存储缓存的值
constructor() {
this.cache = {};//初始化缓存内容
}
//下面这段代码运用了单例模式
static getInstance() {
//仅在第一次创建了一个新的实例,后面都会用到这个实例
if (this.instance) {return this.instance;}
this.instance = new Requset();
return this.instance;
}
//缓存实现
public async request(url: string){
if (this.cache[url]) {
return this.cache[url];
}
const response = await api(url);
this.cache[url] = response;
return response;
}
}
使用:
用JavaScript语法实现:
import {api} from "./utils";
const cache:Record<string,string>={};
export const request = async(url:string)=>{
if(cache[url]){
return cache[url];
}
const response = await api(url);
cache[url] = response;
return response;
}
使用:
⑵发布订阅模式(观察者模式)
定义:一种订阅机制(消息范式),可在被订阅对象发生变化时通知订阅者。
应用场景:从系统架构之间的解耦,到业务中一些实现模式,像邮件订阅,上线订阅等等,应用广泛。
例:
用发布订阅模式实现用户上线订阅
实现代码:
type Notify = (user: User) =>void;
export class User {
name: string;//用户名
status:"offline" | "online ";//用户状态
//followers代表订阅的人
followers: { user: User; notify : Notify }[];
//初始化
constructor(name: string) {
this.name = name;
this.status = "offline" ;
this.followers = [];
}
//订阅方法
//第一个参数:需要订阅的人
//第二个参数:订阅的人上线的通知函数
subscribe(user: User, notify: Notify) {
user.followers. push(i user, notify });
}
online() {
//上线
this.status = "online" ;
//通知所有订阅自己的人
this.followers.forEach(({ notify }) =>{
notify(this);
});
}
}
使用:
2.JavaScript中的设计模式
- 原型模式
- 代理模式
- 迭代器模式
⑴原型模式
定义:复制已有对象来创建新的对象
应用场景:JavaScript对象创建的基本模式
例:
原型链中的原型:每个构造函数都会有一个prototype
属性,prototype
就是调用构造函数所创建的那个实例对象的原型,所有对象实例都可以共享prototype
身上的属性和方法。
用原型模式创建上线订阅中的用户
实现代码:
//作为原型的对象
const baseUser: User = {
name: "",
status: "offline " ,
followers: [],
subscribe(user, notify) {
user.followers.push(i user, notify });
},
online(){
this.status = "online" ;
this.followers.forEach(({ notify })=>{
notify(this);
});
}
}
//基于原型创建新的对象
export const createUser = (name: string) =>{
const user: User = 0bject.create(baseUser);
user.name = name;
user.followers = [];
return user;
};
使用:
⑵代理模式
定义:可自定义控制原对象的访问方式,并且允许在更新前后作一些额外处理。
应用场景:监控、代理工具、前端框架实现等。
使用代理模式实现用户状态订阅
实现代码:
export const createProxyUser = (name: string) =>{
const user = new User (name);
const proxyUser = new Proxy(user,{
set: (target, prop: keyof User, value) =>{
target[prop] = value;
if (prop ≡ "status ") {
notifyStatusHandlers(target, value);
}
return true;
}
});
const notifyStatusHandlers = (user: User,status: "online" | "offline") =>{
if (status ≡ "online") {
user.followers.forEach((i notify })=>{
notify(user);
});
}
}
return proxyUser;
};
⑶迭代器模式
定义:在不暴露数据类型的情况下访问集合中的数据
应用场景:数据结构中有多种数据类型,列表,树等,提供通用操作接口
3.前端框架中的设计模式
- 代理模式
- 组合模式
⑴代理模式:
定义: 为一个对象提供一个代用品或占位符,以便控制对它的访问。
应用场景:
监听变量的变化:前端框架、状态管理库
代理对象的访问方式(拦截器):例如axios
的interceptor
⑵组合模式
定义: 可多个对象组合使用,也可对单个对象独立使用
应用场景: DOM、前端组件、****文件目录、部门
例: React的组件结构
function App(){
return (
<div className="App">
<Header/>
<Count/>
<Footer/>
</div>
);
}
总结
设计模式不是银弹
- 总结出抽象的模式相对比较简单,但是想要将抽象的模式套用到场景中却非常困难
- 现代编程语言的多编程范式带来的更多可能性
- 真正优秀的开源项目学习设计模式并不断实践