如果我们把代码编程比作是战争的话,那么设计模式就是兵法。
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码,让代码更容易被他人理解、保证代码的可靠性。
开闭原则
对扩展开放,对修改关闭。保证程序的扩展性好,易于维护和升级单一职责原则
对一个类而言,应该仅有一个引起它变化的原因里氏代换原则
子类可以扩展父类的功能,但是不能改变父类原有的功能依赖倒置原则
抽象不依赖细节,细节应该依赖抽象。接口隔离原则
建立单一接口,代替庞大臃肿的接口。最小知识原则
一个对象应该对其他对象有最少的了解。类间解耦,弱耦合。保证一个类仅有一个实例,并提供一个访问它的全局访问点
优点
缺点
// 懒汉式,只有在调用 getInstance 的时候才会实例化 Singleton
class Singleton {
static instance = null;
// 获取实例方法
static getInstance() {
return this.instance || (this.instance = new Singleton());
}
}
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 == instance2); // true
// 饿汉式,在类初始化的时候就已经创建好了实例
class Singleton {
static instance = new Singleton();
// 获取实例方法
static getInstance() {
return this.instance;
}
}
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 == instance2); // true
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行
我们明确地计划不同条件下创建不同实例时
优点
缺点
比如我要有一个 Animal
工厂,这个工厂要生产动物。那么我要定义动物都有 Feature
特征必须要有 color
颜色跟 bark
叫声。
// 定义工厂需要的动物特征
interface Feature {
color: string;
bark(): void;
}
// 定义动物类型名字
type name = 'cat' | 'dog'
// 子类必须要实现 Feature 接口的方法
// 这样我们就可以创建白色叫声喵喵喵的猫了
class Cat implements Feature {
color = "白色";
bark() {
console.log(`${this.color} 喵喵喵`);
}
}
// 创建 Dog 类
class Dog implements Feature {
color = "黑色";
bark() {
console.log(`${this.color} 汪汪汪`);
}
}
// 这就是一个动物工厂
class Animal {
createAnimal(type: name) {
switch (type) {
case 'cat':
return new Cat();
case 'dog':
return new Dog();
}
}
}
const animal = new Animal();
const cat = animal.createAnimal('cat');
const dog = animal.createAnimal('dog');
cat.bark()
dog.bark()
abstract class Feature {
abstract color: string;
abstract bark(): void;
}
// 枚举可以使用的动物类型
enum animalType {
'cat',
'dog'
}
// 子类继承抽象类 Feature
// 这样我们就可以创建白色叫声喵喵喵的猫了
class Cat extends Feature {
color = "白色";
bark() {
console.log(`${this.color} 喵喵喵`);
}
}
// 创建 Dog 类
class Dog extends Feature {
color = "黑色";
bark() {
console.log(`${this.color} 汪汪汪`);
}
}
// 这就是一个动物工厂
class Animal {
createAnimal(type: animalType) {
switch (type) {
case animalType.dog:
return new Cat();
case animalType.dog:
return new Dog();
}
}
}
const animal = new Animal();
const cat = animal.createAnimal(animalType.cat);
const dog = animal.createAnimal(animalType.dog);
cat.bark()
dog.bark()
享元模式,运用共享技术,有效地支持大量的细粒度的对象,以避免对象之间拥有相同内容而造成多余的性能开销。
享元(flyweight)模式的主要作用:性能优化,当系统创建过多相似的对象而导致内存占用过高,可以采用这种设计模式进行优化。
享元模式将对象的属性区分为内部状态与外部状态,内部状态在创建的时候赋值,外部状态在实际需要用到的时候进行动态赋值
对于内部状态和外部状态的区分,有几点:
我们要创建 100 个大小相同颜色不同的 div。
interface Div {
width: number;
height: number;
color: string;
}
const divStore: Div[] = [];
class CreateDiv {
public width = 100;
public height = 100;
public color = this.randomColor()
// 随机颜色
private randomColor () {
const color = ['red', 'green', 'blue', 'white', 'black'];
return color[Math.floor(Math.random() * color.length)];
}
}
let count = 100;
while (count--) {
const innerDiv = new CreateDiv();
divStore.push(innerDiv);
}
const sizeof = require('object-sizeof')
console.log(sizeof(divStore)) // 5688
// 将 div 属性设置成内部跟外部两部分
interface Div {
outer: {
width: number;
height: number;
};
innter: {
color: string;
};
}
// 用来储存 Div
const divStore: Div[] = [];
// 创建外部 div 类
class CreateOuterDiv {
width: number = 100;
height: number = 100;
}
class CreateInnerDiv {
public color = this.randomColor()
// 随机颜色
private randomColor () {
const color = ['red', 'green', 'blue', 'white', 'black'];
return color[Math.floor(Math.random() * color.length)];
}
}
// 创建外部 div
const outerDiv = new CreateOuterDiv();
let innerDiv: number;
let count = 100;
while (count--) {
// 创建内部 div
innerDiv = new CreateInnerDiv();
divStore.push({
outer: outerDiv,
innter: innerDiv
});
}
const sizeof = require('object-sizeof')
// 因为这个方法会把引用的对象也全部算一遍,所以我们拆开来算
// 验证:100 * (innerDiv + outerDiv)= 5400 与上面算的 5688 很接近,可以认为这个方法是准确的
console.log(100 * (sizeof(innerDiv) + sizeof(outerDiv))) // 5400
// 100 * innerDiv + outerDiv = 1638
console.log(100 * sizeof(innerDiv) + sizeof(outerDiv)) // 1638
从上面的计算结果来看减少了很大的内存,因为 divStore 数组对象中 outerDiv 其实只有一个,都是它的引用而已。我们的内存占用是 100 * innerDiv + outerDiv,而不使用享元模式的空间是 100 * (innerDiv + outerDiv)
定义一系列的算法, 把它们一个个封装起来, 并且使它们可相互替换。
优点
缺点
在vue中有一个合并选项策略 optionMergeStrategies
,它的功能就是把选项添加一些策略,可以达到我们对选项数据操作的目的
官方例子,将选项 _my_option
添加策略,让它的值加一
Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) {
return child + 1
}
const Profile = Vue.extend({
_my_option: 1
})
// Profile.options._my_option = 2
我们来简单实现一下这个合并选项策略
// 策略模式 store
const optionMergeStrategies: { [prop: string]: any } = {};
// 给 _my_option 添加策略
optionMergeStrategies._my_option = function(value) {
return value + 1
}
// 声明 data
const data = {
// 添加策略
_my_option: 1,
// 未添加策略
option: 1
};
// 响应式
function reactive (data) {
const hander = {
get(target, key, value) {
const v = Reflect.get(target, key, value);
// 此属性存在策略
if (typeof optionMergeStrategies[key] === 'function') {
return optionMergeStrategies[key](v)
}
return v
}
};
return new Proxy(data, hander);
}
const proxy = reactive(data);
// 测试是否添加了响应
proxy._my_option = 10
proxy.option = 10
console.log(proxy._my_option, proxy.option); // 11 10
这样你就可以做更多的事情了,比如验证手机号,邮箱等等,再也不用写很多的 if else 了,而且你也可以随时更换策略。符合了设计模式的开闭原则。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
优点
缺点
比如公众号,有多个人订阅,每天定时发送公众号文章
// 公众号订阅者
abstract class Persen {
abstract update(): void;
protected subject: Subject;
}
// 状态
type state = 'await' | 'publish'
// 依赖
class Subject {
private _state: state = 'await'
// 依赖集合
subs: Persen[] = [];
// 防止频繁设置状态
lock = false
// 设置状态,如果是发布状态的话,就发布文章
set state(state: state) {
// 锁上之后就不能设置状态了,只有锁解开后才可以设置状态
if (this.lock || (this._state = state) === 'await') return;
this.lock = true;
Promise.resolve().then(() => {
this.notify();
this.lock = false;
});
}
// 获得当前状态
get state(): state {
return this._state
}
// 添加订阅
attach(persen: Persen) {
this.subs.push(persen)
}
// 通知更新
notify() {
this.subs.forEach(sub => {
sub.update();
});
}
}
// 创建一个 Tom
class Tom extends Persen {
constructor(subject: Subject) {
super();
subject.attach(this)
}
update() {
console.log('通知到了 Tom');
}
}
// 创建一个 Jick
class Jick extends Persen {
constructor(subject: Subject) {
super();
subject.attach(this)
}
update() {
console.log('通知到了 Jick');
}
}
// 实例化依赖
const subject = new Subject()
// Tom Jick 订阅公众号
new Tom(subject)
new Jick(subject)
// 因为设置了 lock 所以在一次 event loop 只会执行一次
subject.state = 'publish'
subject.state = 'await'
console.log(subject.state) // publish
subject.state = 'publish'
setTimeout(() => {
subject.state = 'publish'
}, 1000)
// 通知到了 Tom
// 通知到了 Jick
// 一秒后...
// 通知到了 Tom
// 通知到了 Jick
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
优点
缺点
比如我们要点击一个按钮,但是这个按钮点击时我们想给他加上埋点并做一些登陆的逻辑
我这里使用了 es7
的语法糖,当然不用语法糖也可以做,但是我觉得用的话更简洁一些
// Button 类,内部有一个 click 方法
// 对click方法做了两个修饰
// 一个是添加埋点,一个是登陆
class Button {
@BuridDecorator
@LoginDecorator
click() {
console.log('点击 dom')
}
}
// 登陆逻辑的装饰器
function LoginDecorator(target, name, descriptor) {
const oriFun = target[name]
descriptor.value = async function() {
const code = await Login();
if (code === 0) {
console.log('登陆成功')
oriFun.call(this, ...arguments)
}
}
}
// 设置埋点的装饰器
function BuridDecorator(target, name, descriptor) {
console.log(`${name} 方法添加了一个埋点`)
}
// 登陆逻辑
async function Login () {
return new Promise((resolve, reject)=> {
setTimeout(() => {
resolve(0)
}, 1000)
})
}
// 点击按钮
const btn = new Button()
btn.click();
// click 方法添加了一个埋点
// 登陆成功
// 点击 dom
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
优点
缺点
举一个例子,你要写一个页面兼容各个端的小程序,那么你就需要根据环境调用不同小程序的 sdk 方法。比如在支付宝中有一个 zhifubaoShare
的分享方法,在微信中有一个 weixinShare
的分享方法。(当然一个sdk还有很多方法,我们只拿分享来举例子)但是我们在工作中其实只希望调用一个 share
方法就能实现不同端的分享。下面我们用适配器模式来做一个 Adapter
适配器。
// =============== 定义接口与类型 ==============================
// 支付宝接口
interface ZhifubaoInerface {
zhifubaoShare(): void;
}
// 微信接口
interface WeixinInterface {
weixinShare(): void;
}
// adapter 接口
interface AdapterInterface {
share(): void;
}
// 合并所有 sdk 类型
interface MergeSdk extends ZhifubaoInerface, WeixinInterface {}
// 支持的平台类型
type platform = 'weixin' | 'zhifubao';
// =============== 代码逻辑实现 ==============================
// 微信 sdk 类实现
class WeixinSdk implements WeixinInterface {
weixinShare() {
console.log('微信分享');
}
}
// 支付宝 sdk 类实现
class ZhifubaoSdk implements ZhifubaoInerface {
zhifubaoShare() {
console.log('支付宝分享');
}
}
// adapter 类实现
class Adapter implements AdapterInterface {
constructor() {
this.sdk = this.getPlatfromSdk();
}
// 挂载 sdk
private sdk: MergeSdk;
// 根据 ua 获取到平台
private getPlatform(): platform {
// 默认写了 weixin
return 'weixin';
}
// 将所有 sdk 方法放进一个 map 里
private getPlatfromSdk() {
const map = {
weixin: WeixinSdk,
zhifubao: ZhifubaoSdk
};
const platform = this.getPlatform();
return new map[platform]() as MergeSdk;
}
// 分享功能
// 实际项目中还有参数的问题,这里为了代码的简洁就不写了
public share() {
const platform = this.getPlatform();
switch (platform) {
case 'weixin':
this.sdk.weixinShare();
break;
case 'zhifubao':
this.sdk.zhifubaoShare();
break;
default:
console.log('此方法不存在');
}
}
}
const adapter = new Adapter();
// 因为我们默认设置了 weixin 平台
adapter.share(); // 微信分享