JS设计模式小结
# 前言
设计模式是编程语言中很重要的一个环节,在合适的地方用上合适的设计模式往往会让代码的逻辑变得更简洁,也更易维护。下面就来介绍一下在JavaScript中几个常用的设计模式吧
# 单例模式
解析
所谓单例模式,就是确保一个类或者一个构造函数只有一个实例,重复执行也只会返回这一个唯一的实例。 他确保了实例的唯一性,并提供全局访问这个实例的方式,他通常会被用在作为一个全局唯一的状态管理器上。任何地方都能访问到,切无法重复创建。
示例:
class Singleton {
constructor(){
if(!Singleton.prototype.instance){
Singleton.prototype.instance = this
}else{
return Singleton.prototype.instance
}
}
}
2
3
4
5
6
7
8
9
10
在上述代码中 我们创建了一个名为Singleton的类,他的初始化函数中首先会去判断当前类的原型链上是否有保存唯一实例,没有的话则保存下当前类的实例 有的话则直接返回原型链上的实例。 通过将实例保存在原型链上的方式 来保证每次new这个类都会返回之前那个唯一的实例。
# 工厂模式
解析
工厂模式提供了一系列封装,就像一个工厂一样,有多条流水线。每条流水线干的活都不一样,由使用者来决定到底采用哪条流水线(用哪个实例)
示例:
function createData(type){
switch (type){
case 'A':
return new DataA()
case 'B':
return new DataB()
case 'C':
return new DataC()
default:
throw new Error('未检测到对应的子类')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
上述代码中我们创建了一个函数createData,由调用者传入对应的参数来实例化对应的类,他就像一个工厂一样。需要传入符合规定的值然后返回相对应的值 他影藏了创建对象的步骤,让使用者只需要关心要创建哪个对象就行了。这样做能极大的提高代码的可读性和可维护性。
# 原型模式
解析
原型模式是一种通过修改原型链来实现继承和共享属性的设计模式
示例:
function user(name){
this.name = name
}
user.prototype.sayName = function (){
return this.name
}
2
3
4
5
6
原型模式其实就比较简单了 主要就是通过修改构造函数的原型链来达到属性共享的目的,但是在使用过程中也需要注意不要随意的修改实例对象的原型 因为这样会导致别得实例的原型也被污染,可能会发生意想不到的问题
# 构造器模式
解析
构造器模式其实就比较常见了,我觉得用专业术语结束反而更让人摸不着头脑。他其实就是一个构造函数,根据传入的参数创建实例。
示例:
function person(name,age){
this.name = name
this.age = age
}
const p1 = new person('小明',18)
console.log(p1.name,p1.age)
2
3
4
5
6
7
8
9
代码很简单就没啥好说的了,构造器模式主要的优点在于将对象创建和初始化的过程分离,使得代码更加的清晰
# 适配器模式
解析
适配器模式的主要用于解决新旧函数的兼容问题,抹平不同接口的差异使得可以在不修改老代码的前提下继续使用老代码的功能
示例:
// 旧函数
function oldFun(a,b){
return a*b
}
// 适配器
function adapt(c){
return oldFun(c,c)
}
2
3
4
5
6
7
8
上述例子中有一个旧函数,接受两个参数并返回他们的乘积。假设我们现在遇到了一个新的场景,求某一个数和自身的乘积。求乘积这个功能在旧函数 中已经实现了,但不同的是旧函数需要接受两个参数,而我们现在是需要求参数与自身的乘积。所以这个时候我们创建了一个适配器,接受一个参数,并在内部 调用旧函数,将参数传入。从而达到不修改旧函数的情况下依然能正常的实现功能
# 外观模式
解析
外观模式其实就是整合一些列的功能提供一个统一的接口 让使用者在实现一个复杂的复合功能时可以用更少的代码完成
示例:
function fn1(){
this.getDate = function (){
return new Date()
}
}
function fn2(){
this.getString = function (){
return 'Hello'
}
}
function fn3(){
this.dateHandler = new fn1()
this.stringHandler = new fn2()
}
fn3.prototype.getDate = function (){
return this.dateHandler.getDate()
}
fn3.prototype.getString = function (){
return this.stringHandler.getString()
}
const fn3Data = new fn3()
console.log(fn3Data.getDate())
console.log(fn3Data.getString())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上述代码中把几个工具函数的实例都收集在了fn3这个构造函数中,通过这样的包装 让fn3也得以拥有其他几个工具函数的功能。 这样做简化了代码的使用,隐藏了内部复杂的细节,使得代码更容易维护
# 代理模式
解析
这个模式大众就比较熟知了,在vue2中就有用到。vue2组件中通过this.xxx能访问到属性就是因为vue将data的属性代理到了this上。使得避免直接访问原始数据。
function hanlde(target){
this.target = target
this.method1 = function () {
return this.target.method1();
}
}
2
3
4
5
6
代理模式其实就是将目标对象的访问方式做一层代理,让我们不直接访问目标对象。通过一个中间者来分发我们的访问,这样做可以在访问的过程中进行一些拦截操作或者副作用操作。是扩展程序的一种方式
# 装饰者模式
解析
装饰者模式允许你动态的像对象添加一些新的属性,并且不更改原对象,这种模式通过创建一个装饰者对象,该对象包装了原始对象。可以继承原始对象的功能 并且能做一些扩展
function component(){
this.say = function (){
console.log('say Hello')
}
}
function Decorator(component){
this.component = component
}
Decorator.prototype.say= function (){
this.component.say()
}
2
3
4
5
6
7
8
9
10
11
12
13
装饰者模式的主要优点是让你在不修改原对象的情况向现有对象添加一些新功能并继承原对象的属性,从而提高代码的可维护性和可扩展性。 它可以让你更灵活得组合对象和重用不同的行为以满足不同的需求
# 观察者模式
解析
观察者模式定义了一种一对多的依赖关系,让多个对象依赖同一个对象的变化。在日常开发过程中也是会经常用到的一种设计模式
class Observer{
constructor(){
this.observers = [] // 存储观察者
}
notify(){
this.observers.forEach(ob=>{
ob()
})
}
addObserver(ob){
this.observers.push(ob)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
观察者模式的使用场景非常多,但凡是涉及到一对多的依赖关系上的处理都可以用观察者模式去完成,被观察者对象会存储观察者的事件,在自身发生某些变化后去派发这些事件通知观察者们做出相应的动作。 在vue2的响应式中就用到了观察者模式
# 发布订阅模式
解析
发布订阅模式其实可以看成是观察者模式的变种,如果说观察者模式是一对多的依赖关系,那发布订阅模式就是多对多的依赖关系。他自身维护一个调度中心,提供了订阅和发布函数。发布函数的调用会触发对应的订阅函数。
示例:
class Publisher {
constructor() {
this.subject = [];
}
publish(message) {
this.subject.forEach(sub => sub(message));
}
subscribe(sub) {
this.subject.push(sub);
}
}
class Subscriber {
constructor() {
this.name = 'Subscriber';
}
receive(message) {
console.log(`${this.name} received message: ${message}`);
}
}
// 创建发布者和订阅者
const publisher = new Publisher();
const subscriber1 = new Subscriber();
const subscriber2 = new Subscriber();
// 订阅消息
publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);
// 发布消息
publisher.publish('Hello, world!');
// 取消订阅
publisher.unsubscribe(subscriber1);
// 再次发布消息
publisher.publish('Goodbye, world!');
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
可以看到发布订阅模式其实就是把订阅者单独抽离出来了,这样做的目的是更好的控制消息的收发。可以更自由的控制订阅的过程
# 策略模式
解析
策略模式其实和工厂模式有点像,他主要是将函数的实现和选择分离开来。也是一个入口对应多个出口,根据传入的不同值来实现不同的输出,内部会选择不同的策略来实现功能。有点像工厂函数一样 内部有多个实现 根据传入的type不同选择不同的实现方式
示例:
// 定义一个策略接口
interface Strategy {
calculatePrice(itemPrice: number): number;
}
// 定义一个具体的策略
class CashStrategy implements Strategy {
calculatePrice(itemPrice: number) {
return itemPrice * 0.9;
}
}
// 定义一个具体的策略
class CreditCardStrategy implements Strategy {
calculatePrice(itemPrice: number) {
return itemPrice * 1.1;
}
}
// 定义一个上下文对象
class Context {
constructor(strategy: Strategy) {
this.strategy = strategy;
}
calculatePrice(itemPrice: number) {
return this.strategy.calculatePrice(itemPrice);
}
}
// 创建一个使用现金支付的上下文对象
const context1 = new Context(new CashStrategy());
// 创建一个使用信用卡支付的上下文对象
const context2 = new Context(new CreditCardStrategy());
// 计算商品价格
console.log('使用现金支付的价格:' + context1.calculatePrice(100));
console.log('使用信用卡支付的价格:' + context2.calculatePrice(100));
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
这里我们定义了一个基类Strategy,它内部声明了一个属性calculatePrice,值是一个函数,但我们并没有在基类中去实现这个函数。这个基类更像是一个枚举集合一样具体的实现需要我们后续创建新的类去完善, 就像CashStrategy类和CreditCardStrategy类这样,他俩就是去实现了Strategy内部的calculatePrice属性。然后我们就可以根据需求去调用不同的类来实现不同的功能
# 迭代模式
解析
迭代器模式提供了一种统一的方式来遍历集合中的元素,而不需要暴露集合的内部结构。迭代器模式将遍历集合的责任从集合本身转移到了迭代器对象上,从而使得集合的内部结构可以灵活地更改,而不会影响到使用迭代器的代码。
示例:
// 定义一个迭代器接口
interface Iterator {
next(): any;
hasNext(): boolean;
}
// 定义一个具体的迭代器
class ConcreteIterator implements Iterator {
constructor(items: any[]) {
this.items = items;
this.index = 0;
}
next() {
if (this.index < this.items.length) {
return this.items[this.index++];
}
return null;
}
hasNext() {
return this.index < this.items.length;
}
}
// 定义一个聚合类
class Aggregate {
constructor() {
this.items = [];
}
add(item: any) {
this.items.push(item);
}
iterator() {
return new ConcreteIterator(this.items);
}
}
// 创建聚合类的实例
const aggregate = new Aggregate();
// 添加元素
aggregate.add(1);
aggregate.add(2);
aggregate.add(3);
// 创建迭代器并遍历元素
const iterator = aggregate.iterator();
while (iterator.hasNext()) {
const item = iterator.next();
console.log(item);
}
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
迭代器模式主要就是自己实现一个可迭代对象的类:内部维护一个计数器,提供一个迭代函数每次调用都会返回当前索引的值。
剩下还有模板方法模式、命令模式、组合模式、状态模式、责任链模式、中介者模式、解释器模式、桥梁模式、访问者模式、调停者模式等等常用的23中设计模式。
# 结语
设计模式在程序设计中有着非常重要的地位,一段可维护性高,可扩展性高的好代码一定离不开设计模式。精读这些设计模式能让编码能力更上一层楼!