maiy's blog

设计模式

2020-08-13

设计模式

不看设计模式,写出来的代码永远不会漂亮

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。惰性单例模式是指在使用的时候才回进行创建,并且把创建对象和管理单例的职责分开。

  • 管理单例
    1
    2
    3
    4
    5
    // 该方法无任何业务逻辑
    getSingle(fn) {
    let instance;
    return () => (instance || (result = fn.apply(this, arguments)));
    }
  • 创建对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 创建单例对象的义务逻辑
    createLoginLayer() {
    const div = document.createElement('div');
    div.innerHTML = '我是登陆浮窗';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
    }
    const createSingleLoginLayer = getSingle(createLoginLayer);
    // 点击的时候才创建
    document.getElementById('loginBtn').onclick = function() {
    const loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block';
    }

    策略模式

    定义一系列的算法,把他们一个个封装起来,并且使它们可以相互替换。一个策略模式程序至少有两部分组成,第一部分是一组策略类,封装了所有的算法。另一部分是环境类Context,接受请求然后委托到某个策略类。当一个类有过多的if else时,就需要考虑使用策略模式进行重构了。
  • 利用组合、委托和多态思想,有效的避免多重条件选择语句
  • 开发-封闭原则的完美支持,讲算法封装在strategy中,使得它容易切换、易于理解和扩展
  • 复用
  • 例子:表单校验
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // 一系列的算法策略
    const strategies = {
    isNotEmpty(value, errorMsg) {
    if (value === '') {
    return errorMsg;
    }
    },
    minLength(value, length, errorMsg) {
    if (value.length < length) {
    return errorMsg;
    }
    },
    isMobile(value, errorMsg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
    return errorMsg;
    }
    }
    }
    // 接收请求,然后委托给策略算法
    function Validator() {
    this.cache = []; // 保存一系列的校验规则
    }
    Validator.prototype.add = function(value, rule, errorMsg) {
    const ary = rule.split(':'); // 把strategy和参数分开,如minLength:6
    this.cache.push(() => { // 把校验的步骤存起来
    const strategy = ary.shift(); // 用户的strategy
    ary.unshift(value); // 把input的value添加到参数列表
    ary.push(errorMsg)
    return strategies[strategy].apply(value, ary);
    });
    }
    Validator.prototype.start = function() {
    for (let i = 0; i < this.cache.length; i++) {
    const func = this.cache[i];
    const msg = func();
    if (msg) {
    return msg;
    }
    }
    }
    // 使用模式
    const validator = new Validator();
    validator.add(name, 'isNotEmpty', '用户名不能为空');
    validator.add(paddword, 'minLength:6', '密码长度不能少于6位');
    const errorMsg = validator.start(); // 通过判断errorMsg就可以判断是否进行表单请求

代理模式

代理模式是为一个对象提供一个代用品,通过代用品来对这个对象进行访问。常用的有保护代理、虚拟代理和缓存代理。保护代理是指代理负责拒绝一些不适合的请求,只接受适合的请求;虚拟代理是指等到需要使用时才去创建使用对象。缓存代理是指在代理把请求的结果缓存起来,下次再请求查找如果有缓存就直接取缓存。

  • 虚拟代理实现图片预加载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 该类的责职是加载图片
    const myImage = (() => {
    const imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
    setSrc(src) {
    // 图片加载完成后再更新目标图片
    imgNode.src = src;
    }
    }
    })();
    // 代理类的责职是预加载
    const proxyImage = (() => {
    // 新建一个image下载图片
    const img = new Image();
    img.onload = function() {
    myImage.setSrc(this.src); // 下载完成后给目标对象设置
    }
    return {
    setSrc(src) {
    myImage.setSrc('file:///loading.png'); // 下载的时候显示个预加载菊花
    img.src = src // 开始下载图片
    }
    }
    })();
    proxyImage.setSrc('https://www.baidu.png');
  • 缓存代理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // 乘法
    function mult() {
    const a = 1;
    for (let i = 0; i < arguments.length; i++) {
    a = a * arguments[i];
    }
    return a;
    }
    // 加法
    function plus() {
    const a = 0;
    for (let i = 0; i < arguments.length; i++) {
    a += arguments[i];
    }
    return a;
    }
    // 责职是缓存,可以给加减乘除复用
    function createProxyFactory(fn) {
    const cache = {}; // 用户缓存结果
    return function() {
    const args = Array.prototype.join.call(arguments, ',');
    if (args in cache) { // 判断有无缓存,有则返回缓存
    return cache[args];
    }
    // 缓存结果
    return cache[args] = fn.apply(this, arguments);
    }
    }
    const proxyMult = createProxyFactory(mult);
    const proxyPlus = createProxyFactory(plus);
    proxyMult(1, 2, 3, 4); // 输出24
    proxyMult(1, 2, 3, 4); // 输出缓存的24

发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。优点是可以在时间和对象之间解耦,缺点是不容易追溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const Event = (function() {
const clientList = {};
// 用于保存监听的函数
const listen = function(key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn); // 存储订阅的消息
};
// 通知监听函数
const trigger = function() {
const key = Array.prototype.shift.call(arguments);
const fns = clientList[key];
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (let i = 0; i < fns.length; i++) {
fns[i].apply(this, arguments);
}
}
// 取消订阅
const remove = function(key, fn) {
const fns = clientList[key];
if (!fns) {
return false;
}
if (!fn) { // 如果没有传函数,则取消key对应的所有订阅
fns && (fns.length = 0);
} else {
for (let i = 0; i < fns.length; i++) {
const _fn = fns[i];
if (_fn === fn) {
fns.splice(i, 1);
}
}
}
}

return {
listen,
trigger,
remove
}
})()

Event.listen('buy', (arguments) => {
console.log('buy', arguments);
})
Event.listen('buy', (arguments) => {
console.log('buy1', arguments);
})
let a = (arguments) => {
console.log('buy2', arguments);
}
Event.listen('buy', a)
Event.listen('buy', (arguments) => {
console.log('buy3', arguments);
})

Event.trigger('buy', 111);
Event.remove('buy', a);
Event.trigger('buy', 222);

命令模式

命令模式的作用主要是将动作的请求者动作的执行者解耦。抽象一个command类,它定义了一个执行操作的接口(execute),还包含一个接收者(真正的执行命令的对象)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 安装命令
const setCommand = (button, command) => {
button.onclick = function() {
// 抽象command对象,执行命令
command.execute();
}
}
// receive,具体的命令执行者
const MenuBar = {
refresh() {
console.log('刷新菜单目录');
}
}
// 命令类,封装了命令的行为
class RefreshMenuBarCommand {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.refresh();
}
unExecute() {
// 撤销操作
this.receiver.unRefresh();
}
}

const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);

setCommand(document.getElementById('button'), refreshMenuBarCommand);

组合模式

组合模式又叫部分整体模式,依据树形结构来组合对象,用来表示部分以及整体层次,利用对象多态性统一对待组合和单个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 叶对象
class Folder {
constructor(name) {
this.name = name;
this.files = [];
}
add(file) {
this.files.push(file);
}
scan() {
console.log(`开始扫描文件夹:${this.name}`);
for (let i = 0; i < this.files.length; i++) {
this.files[i].scan();
}
}
}
// 叶节点
class File {
constructor(name) {
this.name = name;
}
add(name) {
throw new Error('文件下面不能再添加文件');
}
scan() {
console.log(`开始扫描文件:${this.name}`)
}
}
const folder = new Folder('学习资料');
const folder1 = new Folder('Javascript');
const folder2 = new Folder('jQuery');

const file1 = new File('Javascript 设计模式与开发实践');
const file2 = new File('精通jQuery');
const file3 = new File('重构与模式');

folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3)

folder.scan();

模版方法模式

模版方法模式是一种利用继承方式,把一系列执行步骤抽象封装在父类(模板),然后在子类实现不同的算法(按照模板具体实现)。在JavaScript中也可以使用高阶函数实现,不需要使用继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Beverage { // 抽象类
init() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.customerWantsCondiments()) { // 如果挂钩返回了true,则需要调料
this.addCondiments();
}
}

boilWater() {
console.log('把水煮沸');
}

brew() {
throw new Error('子类必须重写brew方法');
}

pourInCup() {
throw new Error('子类必须重写addCondiments方法');
}

customerWantsCondiments() {
return true; // 默认需要调料
}
}

class CoffeeWithHook extends Beverage { // 继承饮料抽象类
brew() {
console.log('用沸水把冲泡咖啡');
}

pourInCup() {
console.log('把咖啡倒进杯子');
}

addCondiments() {
console.log('加糖和牛奶');
}

customerWantsCondiments() {
return false; // 请求客户是否需要加糖和牛奶
}

}

const coffee = new CoffeeWithHook()
coffee.init();

享元模式

享元模式是一种用于性能优化的模式,核心是使用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。

责职链模式

责职链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。缺点是如果责职链过长时会影响性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Chain {
constructor(fn) {
this.fn = fn;
this.successor = null;
}
// 设置下一个节点
setNextSuccessor(successor) {
return this.successor = successor
}
// 是否把请求丢到下一个节点
passRequest() {
const ret = this.fn.apply(this, arguments);
if (ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret
}
// 异步情况下,如果需要丢给下个节点,则调用该方法
next() {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
}

const fn1 = new Chain(() => {
console.log(1);
return 'nextSuccessor';
});

const fn2 = new Chain(function() {
console.log(2);
setTimeout(() => {
this.next();
}, 1000);
});

const fn3 = new Chain(() => {
console.log(3);
});

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();
// 输出1、2、3

中介者模式

用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要县式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。当场景是对象与对象之间存在大量的关联,就可以考虑中介者模式。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章