本文简单记录了JS中的一些常见设计模式。
工厂模式
工厂模式是用来创建对象的一种最常用的设计模式。我们不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。工厂模式根据抽象程度的不同可以分为:简单工厂
,工厂方法
和抽象工厂
。
1. 简单工厂模式
简单工厂模式
(Simple Factory)又叫静态工厂模式
,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。简单工厂只能作用于创建的对象数量较少,对象的创建逻辑不复杂时使用。
function createBook(name, time, type) {
var o = new Object();
o.name = name;
o.time = time;
o.type = type;
o.getName = function() {
return this.name;
};
return o;
}
var book1 = createBook('js book', 2020, 'js');
var book2 = createBook('css book', 2021, 'css');
book1.getName();
book2.getName();
复制代码
2. 工厂方法模式
工厂模式(Factory Method)定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
class User {
constructor(name = '', viewPage = []) {
if(new.target === User) {
throw new Error('抽象类不能实例化!');
}
this.name = name;
this.viewPage = viewPage;
}
}
class UserFactory extends User {
constructor(name, viewPage) {
super(name, viewPage)
}
create(role) {
switch (role) {
case 'superAdmin':
return
new UserFactory('超级管理员', ['首页', '通讯录', '发现页', '权限管理']);
break;
case 'admin':
return new UserFactory('普通用户', ['首页', '通讯录', '发现页']);
break;
case 'user':
return new UserFactory('普通用户', ['首页', '通讯录', '发现页']);
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
}
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('user');
复制代码
3. 抽象工厂模式
抽象工厂模式(Abstract Factory):通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类产品的实例。
简单工厂模式和工厂方法模式都是直接生成实例,但是抽象工厂模式不同,抽象工厂模式并不直接生成实例, 而是用于对产品类簇的创建。
function getAbstractUserFactory(type) {
switch (type) {
case 'wechat':
return UserOfWechat;
break;
case 'qq':
return UserOfQq;
break;
case 'weibo':
return UserOfWeibo;
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
let WechatUserClass = getAbstractUserFactory('wechat');
let QqUserClass = getAbstractUserFactory('qq');
let WeiboUserClass = getAbstractUserFactory('weibo');
let wechatUser = new WechatUserClass('微信小李');
let qqUser = new QqUserClass('QQ小李');
let weiboUser = new WeiboUserClass('微博小李');
复制代码
工厂模式主要应用场景:jQuery的选择器$
,Vue 异步组件
。
4. 单例模式
单例模式(Singleton):又称为单体模式,是只允许实例化一次的对象类。
单例模式具备以下特点:
- 系统中被唯一使用
- 一个类只有一个实例(注意只能有一个实例,必须是强相等===)
let Modal = (function(){
let instance;
return function(name) {
if (instance) {
return instance;
}
this.name = name;
return instance = this;
}
})();
Modal.prototype.getName = function() {
return this.name
}
let question = new Modal('问题框');
let answer = new Modal('回答框');
console.log(question === answer); // true
console.log(question.getName()); // '问题框'
console.log(answer.getName()); // '问题框'
复制代码
单例模式的实现实质即创建一个可以返回对象实例的引用和一个获取该实例的方法。保证创建对象的引用恒唯一。
单例模式的主要应用场景:Vuex
和 redux 中的 store
。
5. 适配器模式
将一个类(对象)的接口(方法或属性)转化为另外一个接口,以满足用户需求,使类(对象)之间接口不兼容问题通过适配器得以解决。
class Plug {
getName() {
return 'iphone充电头';
}
}
class Target {
constructor() {
this.plug = new Plug();
}
getName() {
return this.plug.getName() + ' 适配器Type-c充电头';
}
}
let target = new Target();
target.getName(); // iphone充电头 适配器转Type-c充电头
复制代码
优点:
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 适配对象,适配库,适配数据
缺点:
- 额外对象的创建,非直接调用,存在一定的开销(且不像代理模式在某些功能点上可实现性能优化)
- 如果没必要使用适配器模式的话,可以考虑重构,如果使用的话,尽量把文档完善
场景:
- 整合第三方SDK
- 封装旧接口
- vue的computed
6. 代理模式
代理模式(Proxy):由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。
通俗来说,代理模式要突出“代理”的含义,该模式场景需要三类角色,分别为使用者、目标对象和代理者,使用者的目的是直接访问目标对象,但却不能直接访问,而是要先通过代理者。因此该模式非常像明星代理人的场景。其特征为:
- 使用者无权访问目标对象;
- 中间加代理,通过代理做授权和控制。
class ReadImg {
constructor(fileName) {
this.fileName = fileName;
this.loadFromDisk();
}
display() {
console.log('display...' + this.fileName);
}
loadFromDisk() {
console.log('loading...' + this.fileName);
}
}
class ProxyImg {
constructor(fileName) {
this.readImg = new ReadImg(fileName)
}
display() {
this.readImg.display();
}
}
let proxyImg = new ProxyImg('1.png');
proxyImg.display();
复制代码
代理模式的主要应用场景:
- HTML元素事件代理
- $.proxy
- ES6 Proxy
7. 迭代器模式
迭代器模式(Iterator):在不暴露对象内部结构的同时,可以顺序地访问聚合对象内部的元素。
迭代器模式应用场景:
- Array.prototype.forEach
- jQuery中的$.each()
- ES6 Iterator
8. 策略模式
策略模式(Strategy):将定义的一组算法封装起来,使其相互之间互相替换。封装的算法具有一定的独立性,不会随客户端变化而变化。
策略模式使用场景:场景例子
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 表单验证
9. 观察者模式
观察者模式(Observer):又被称为发布-订阅模式或消息模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
观察者模式的作用:
- 可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。
- 一个对象不用再显式地调用另外一个对象的某个接口。让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。
两个优点:时间上的解耦,对象之间的解耦。
观察者模式应用场景:
-
DOM事件
-
自定义事件(Event)
-
Node.js中的 EventEmitter事件监听器
-
Vue响应式
-
Vue 的 Watch生命周期钩子
10. 装饰者模式
装饰者模式(Decocator):在不改变原对象的基础上,通过对其进行包装扩展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。
因此其特征主要有两点:
- 为对象添加新功能;
- 不改变其原有的结构和功能,即原有功能还继续会用,且场景不会改变。
优点:
- 装饰类和被装饰类都只关心自身的核心业务,实现了解耦。
- 方便动态的扩展功能,且提供了比继承更多的灵活性。
class Circle {
draw() {
console.log('画一个圆形');
}
}
class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
this.circle.draw();
this.setRedBorder(circle);
}
setRedBorder(circle) {
console.log('画一个红色边框');
}
}
let circle = new Circle();
let decorator = new Decorator(circle);
decorator.draw(); //画一个圆形,画一个红色边框
复制代码
11. 状态模式
状态模式(State):当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象。
12. 命令模式
命令模式(Command):将请求与实现解耦并封装成独立对象,从而使不同的请求堆客户端的实现参数化。
13. 桥接模式
桥接模式(Bridge):在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。
14. 组合模式
组合模式(Composite):又称部分-整体模式,将对象组合成树形结构以表示 “部分整体” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
15. 享元模式
享元模式(Flyweight):运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。
16. 模板方法模式
模板方法模式(Template Method):父类中定义一组操作算法骨架,而将一些实现步骤延迟到子类中,使得子类可以不改变父类的算法结构的同时可重新定义算法中某些实现步骤。
17. 责任链模式
责任链模式(Chain of Responsibility):解决请求的发送者与请求的接受者之间的耦合,通过职责链上的多个对象对分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求的处理。
18. 中介者模式
中介者模式(Mediator):通过中介者对象封装一系列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合。有时中介者对象也可改变对象之间的交互。