史上最全的JS设计模式(二)

2,691 阅读21分钟

前情回顾

我们在​ 史上最全的JS设计模式(一) 中主要介绍了设计模式的定义、好处、原则和类别,并详细解释了13种设计模式。

本文将继续介绍剩下的9经典模式,以及设计模式的JS代码示例。

类型
目的
模式
核心
创建型
**处理对象创建机制**

使得程序可以更加灵活的判断针对某个给定实例需要创建哪些对象。

单例模式
确保一个类只有一个实例,并提供对该实例的全局访问。
简单工厂模式
一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂模式
定义一个接口用于创建对象,但是让子类决定初始化哪个类。工厂方法把一个类的初始化下放到子类。
抽象工厂模式
为一个产品族提供了统一的创建接口。当需要这个产品族的某一系列的时候,可以从抽象工厂中选出相应的系列创建一个具体的工厂类
建造者模式
封装一个复杂对象的构建过程,并可以按步骤构造
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
结构型
**处理对象的组合**

将对象结合在一起形成更大的结构

代理模式
通过替身对象实现对访问动作的控制和处理
适配器模式
将一个类的方法接口转换成客户希望的另外一个接口
装饰器模式
动态的给对象添加新的功能
组合模式
将对象组合成树形结构以表示“”部分-整体“”的层次结构
享元模式
通过共享技术来有效的支持大量细粒度的对象
外观模式
对外提供一个统一的方法,来访问子系统中的一群接口
桥接模式
将抽象部分和它的实现部分分离,使它们都可以独立的变化
行为型
**改善或者简化系统中不同对象之间的通信**
观察者模式
定义了对象一对多的依赖关系。当目标对象状态发生变化后,会通知到所有的依赖对象
模版模式
抽象父类定义抽象方法和具体运行策略,来制定子类的运行顺序和机制;
具体子类来重写父类的抽象方法
策略模式
定义多个策略类实现具体算法
定义一个环境类通过请求参数来决定使用哪些策略
状态模式
允许一个对象在其对象内部状态改变时改变它的行为
中介者模式
用一个中介对象来封装一系列的对象交互
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,不需要关心对象的内部构造
备忘录模式
在不破坏封装的前提下,保持对象的内部状态
访问者模式
在不改变数据结构的前提下,增加作用于一组对象元素的新功能
解释器模式
给定一个语言,定义它的文法的一种表示,并定义一个解释器
命令模式
将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
职责链模式
将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会

【创建型】

建造者模式

大家做组件/大型方案的时候会面临构建一个**复杂对象**的场景,这个对象的每个部分都由一些算法/逻辑生成,并且这些部分都随时可能变化,并且属性组合也存在一定逻辑和步骤。

建造者模式就解决这种场景的创建型设计模式,它可以将一个复杂的对象分解成多个简单的对象来进行构建,将复杂的构建层与表现层分离,使相同的构建过程可以创建不同的表示模式。

建造者模式的主要结构和过程如上图所示,主要包括了Product产品、建造者Builder、Director指挥者这3种类。其中Product定义这个复杂对象的属性和结构,Builder定义每个属性的生成函数,Director负责分步调用Builder的各个函数,生成具体对象实例。

举一个比较通俗的例子,我需要生产一个奔驰车:首先需要定义一个Car的Product类,然后定义一个BenzBuilder建造者和CarDirector指挥者

// 定义Car的Product类
function Car(){    
    this.color='' // 颜色    
    this.brand='' // 品牌    
    // ...其他复杂属性
}

// 定义BenzBuilder建造者
function BenzBuilder(){
    this.car = new Car()
    this.genColor =()=>{
        this.car.color = 'black'
    }
    this.genBrand = ()=>{
        this.car.brand = 'benz'
    }
}

// 定义CarDirector指挥者
function CarDirector(){
    this.genCar = (builder)=>{
        // 分步调用builder的函数,生成Car对象的属性
        builder.genColor()
        builder.genBrand()
        return builder.car
    }
}
最后通过调用Diretor,指挥具体的Builder生成特定的Car
var benzBuilder = new BenzBuilder()
var director = new CarDirector()
// 调用
var car = director.genCar(benzBuilder) // Car {color: "black", brand: "benz"}
如果我们突然又要生产一个宝马,因为生产对象和属性组装的逻辑是一样的。所以我们可以直接定义BaomaBuilder,进行指挥生产。
// 定义BenzBuilder建造者
function BaomaBuilder(){
    this.car = new Car()
    this.genColor =()=>{
        this.car.color = 'red'
    }
    this.genBrand = ()=>{
        this.car.brand = 'baoma'
    }
}

var baomaBuilder = new BaomaBuilder()
// 调用
var car2 = director.genCar(baomaBuilder) //Car {color: "red", brand: "baoma"}

综上我们可以看到,建造者模式的适用条件有:

  • 对象(Product)的属性/结构复杂

  • 对象的属性生成是有一定逻辑和变化的(不同的Builder有不同的属性生成函数)

  • 不同实例对象的属性组装顺序是不变的(Director指挥顺序不变)

优点

  • 可拓展性高,碰到新的需求,只需要实现一个新的建造者

缺点

  • 对Product对象有要求,需要抽取满足各类情况的复杂对象

  • 指挥者的调用顺序不能变,构建过程必须相同

区别:

  • 与工厂模式的区别:建造者关注过程,由指挥者调用构建过程;工厂模式不关注过程

  • 与外观模式、模版模式的区别:建造者是创建复杂结构的对象(创建型),外观模式是整合一群接口对外提供统一方法(结构型),模版模式是按照模版执行动作(行为型)

推荐程度:⭐️⭐️⭐️⭐️

【结构型】

享元模式

当我们面临对大量重复对象进行处理时,如果创建多个对象,会使得系统内存占用过多,造成系统资源的浪费。

享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现多次复用,使得系统中的对象个数大大减少。同时享元模式使用了内部状态和外部状态,同时外部状态相对独立,不会影响到内部状态,所以享元模式能够使得享元对象在不同的环境下被共享。

网上摘抄一个不错的栗子🌰 :要对某个班进行身体素质测量,根据性别的区分来测量身高体重来进行评判
// 健康测量
function Fitness(name, sex, age, height, weight) {
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.height = height;
    this.weight = weight;
}

// 开始评判
Fitness.prototype.judge = function() {
    var ret = this.name + ': ';
    ret += this.sex === 'male'?this.judgeMale(): this.judgeFemale()
    console.log(ret);
};

// 男性评判规则
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// 女性评判规则
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};

var a = new Fitness('A', 'male', 18, 160, 80);
var b = new Fitness('B', 'male', 21, 180, 70);
var c = new Fitness('C', 'female', 28, 160, 80);
var d = new Fitness('D', 'male', 18, 170, 60);
var e = new Fitness('E', 'female', 18, 160, 40);

// 开始评判
a.judge(); // A: false
b.judge(); // B: false
c.judge(); // C: false
d.judge(); // D: true
e.judge(); // E: true
评判五个人就需要创建五个对象,一个班就需要创建几十个对象 :(

因为性别是主要影响评判方法的属性,所以可以把性别看做内部状态即可,其他属性都属于外部状态。将对象的公共部分(内部状态)抽离出来,与外部状态独立。

这么一来我们只需要维护男和女两个对象(使用factory对象),而其他变化的部分则在外部维护(使用manager对象)
// 健康测量
function Fitness(sex) {
    this.sex = sex;
}

// 工厂,创建可共享的对象
var FitnessFactory = {
    objs: [],
    create: function(sex) {
        if (!this.objs[sex]) {
            this.objs[sex] = new Fitness(sex);
        }
        return this.objs[sex];
    }
};

// 管理器,管理非共享的部分
var FitnessManager = {
    fitnessData: {},

    // 添加一项
    add: function(name, sex, age, height, weight) {
        var fitness = FitnessFactory.create(sex);
       // 存储变化的数据
        this.fitnessData[name] = {
            age: age,
            height: height,
            weight: weight
        };
        return fitness;
    },

    // 从存储的数据中获取,更新至当前正在使用的对象
    updateFitnessData: function(name, obj) {
        var fitnessData = this.fitnessData[name];
        for (var item in fitnessData) {
            if (fitnessData.hasOwnProperty(item)) {
                obj[item] = fitnessData[item];
            }
        }
    }
};

// 开始评判
Fitness.prototype.judge = function(name) {
    // 操作前先更新当前状态(从外部状态管理器中获取)
    FitnessManager.updateFitnessData(name, this);

    var ret = name + ': ';
    ret += this.sex === 'male'?this.judgeMale(): this.judgeFemale()
    console.log(ret);
};

// 男性评判规则
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// 女性评判规则
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};

var a = FitnessManager.add('A', 'male', 18, 160, 80);
var b = FitnessManager.add('B', 'male', 21, 180, 70);
var c = FitnessManager.add('C', 'female', 28, 160, 80);
var d = FitnessManager.add('D', 'male', 18, 170, 60);
var e = FitnessManager.add('E', 'female', 18, 160, 40);

// 开始评判
a.judge('A'); // A: false
b.judge('B'); // B: false
c.judge('C'); // C: false
d.judge('D'); // D: true
e.judge('E'); // E: true
这样使得我们只需要维护2个对象就可以完成整个班级几十个同学的测量工作,但是也使得系统的**复杂度显著的提高...**

应用场景:

  • 存在大量重复的对象需要处理

优点:

  • 节省内存,避免大量创建重复对象

缺点:

  • 享元模式要求能够共享的对象必须是细粒度对象

  • 享元模式会使得系统变得更加复杂

推荐程度:⭐️⭐️

桥接模式

当系统存在多个维度的变化时,我们需要将这些维度分类抽离出来,使得各个维度能够独立变化,减少这些变化的耦合,这个抽离的过程就使用了桥接模式。

所谓桥接,指的是将抽象和实现部分进行分离和连接的方式。桥接模式可以将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。

说了这么多,是不是有点迷糊?简单的来说,桥接模式就是函数的抽取,我们将某些动作抽取成独立函数,在具体业务运行时去调用各个函数来实现特殊的复杂结构。

比如说小阮是一个高大帅气的男孩,小迟是一个高大聪明的男孩。我们可以将高大、帅气、聪明抽取成单独的特性类,由不同的特性类去完成具体同学的组装。

// 定义特性类
class Tall {
    constructor(height){
        this.height = height
    }

    descript(){ console.log('您可真高') }
}

class Handsome {
     descript(){ console.log('帅气!') }
}

class Smart {
     descript(){ console.log('别人家的孩子真聪明') }
}

// 定义具体实现类
// 小阮是一个高大帅气的男孩
class XiaoRuan {
    constructor(height){
        this.height = new Tall(height)
        this.face = new Handsome()
    }

    init(){
        this.height.descript()
        this. face.descript()
    }
}

// 小迟是一个高大聪明的男孩
class XiaoChi {
    constructor(height){
        this.height = new Tall(height)
        this.head = new Smart()
    }

    init(){
        this.height.descript()
        this.head.descript()
    }
}
可以看到两个同学有公用的函数,也有不同的函数,这些函数都是描述不同维度的属性。
桥接模式正是将这些维度抽离成抽象类,然后在不同的具体实现类中进行组装和调用。同时对象也可以随时改变自己的实现工程,将抽象和实现进行充分的解耦,从而产生更好的结构化系统。

应用场景:

  • 系统存在多个维度的变化,需要将这些维度抽离出来,让其独立变化

优点:

  • 遵行单一职责、开闭原则,系统拓展性好

  • 抽象和实现充分解耦

缺点:

  • 会有大量的抽象类,增加开发成本

区别:

  • 与建造者模式都是构建复杂对象/结构,比较容易混淆。但是建造者是创建型模式,需要先定义稳定的Product类,后续都是为了构建Product实例,如果需要增加Product属性,需要同步修改所有的Builder的方法、Director的调用过程。而桥接模式是组合型模式,通过不同的类组合成复杂结构,如果需要增加实例的属性,只需要增加额外的抽象类即可。

  • 与装饰者模式的区别是,装饰者是围绕一个已有的实例类来进行装饰增强,而桥接模式是通过多个抽象类来组装一个对象。

推荐程度:⭐️⭐️⭐️

【行为型】

迭代器模式

**迭代器模式**允许我们遍历对象里的所有元素,而无需公开对象的内部表示。
大家看名字(Iterator迭代器)是不是有点眼熟,其实JS中Array、Object、Map、Set这四种数据集合都拥有满足迭代器模式的遍历方法。
eg:
Array.forEach    Object.keys   Map.forEach    Set.forEach

如【阮一峰ES6 Iterator/for of】中描述,迭代器主要的作用是:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;

如果要实现一个迭代器模式,需要有index指针、next函数、value当前对象、done状态。下面我们实现一个简单的迭代器:
function makeIterator(array,cb) {
  var nextIndex = 0;
  return {
    next: function() {
      var result= nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
      cb(result);
      return result
    },
    init:function(){
        var result ={}
        while(result.done!==true){ result=this.next()  }
    }
  };
}

var it = makeIterator(['a', 'b'], console.log);
it.init()
// { value: "a", done: false }
// { value: "b", done: false }
// { value: undefined, done: true }

应用场景:

  • 需要遍历访问对象里的所有元素进行操作

优点:

  • 可以顺序遍历元素,而不需要关心对象内部元素的具体形式

缺点:

  • -

推荐程度:⭐️⭐️⭐️⭐️

备忘录模式

备忘录模式,顾名思义像一个小本本,记录着我们的安排和想法,方便随时翻阅和回忆。在软件开发中,备忘录模式主要是在不破坏分装性的前提下,去捕获和记录对象的状态,提供对象状态恢复的能力,常用于记录缓存、记录历史、撤销操作

因为这个模式比较好理解,所以我们只文字举例:
  1. 前端权限存储:我们请求单个页面的权限数据后,通常会缓存到浏览器中,本次会话中如果再次访问了这个页面,可以取出缓存的权限数据进行处理。
  2. 撤销操作:在流程设计、应用中心页面设计中,通常会存储用户历史操作,通过读取history来撤销对schema的修改

适用场景:

  • 保存一个对象在某一时刻的全部或部分状态

优点:

  • 提供了一种状态恢复的时间机制,使得用户可以方便的回退到一个特定的历史步骤。

  • 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。

缺点:

  • 备忘录模式的主要缺点是资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免的需要占用大量的存储空间。

推荐程度:⭐️⭐️⭐️

访问者模式

**访问者模式**的主要目的是想将数据结构和数据操作分离,解决稳定的数据结构和频繁变化的操作方法耦合的问题。访问者模式主要用于操作特殊结构的对象或者一系列相同结构对象。
下面我们实战一下,需要大家来设计雇员管理系统,用于对雇员的剩余假期进行财务结算发放福利。

人员结构定义如下

class Employee {
    constructor(name, remainingHolidays, dgree, performance){
        this.name=name //名称
        this.remainingHolidays= remainingHolidays // 剩余假期天数
        this.dgree=dgree // 雇员级别 1初级,2中级,3高级
        this.performance=performance // 绩效 1不及格 2及格 3优秀
    }
}

人员组结构定义如下

class  EmployeeGroup {
    this.employeeMap=new Map()
    // 新增员工
    addEmployee(employee){
        this.employeeMap.set(employee.name, employee)
    }
}
在今年对雇员的剩余假期,公司打算采用 【 200元 x(剩余天数 x 级别 x 绩效)】的方式进行福利转换,换成钱发给员工。那么我们应该如何设计这个功能呢?

---我是答案分割线---

窝漕,这不是很简单吗,EmployeeGroup增加一个遍历循环不就搞定了吗?

class  EmployeeGroup {
    this.employeeMap=new Map()

    addEmployee(employee){
        this.employeeMap.set(employee.name, employee)
    }

    // 遍历输出结果
    countMoney(){
        for(let employee of this.employeeMap.values()){
            const {name,remainingHolidays, dgree, performance} = employee
            const result= 200* remainingHolidays * dgree * performance
            console.log(name, result )
        }
    }

}
看起来没什么毛病。但是如果18年是这个规则,19年却要变成 【 100元 x(剩余天数 x ( 级别 +3 ) x ( 绩效 +2 ) )】怎么办。我们需要直接修改EmployeeGroup函数的countMoney方法吗?这样违背了设计模式的开闭原则,我们应该可以对新增开发,对修改关闭。

访问者模式就可以用于解决这个问题

首先我们对Employee对象和EmployeeGroup对象进行增强,增加accept方法器接受访问者Visitor

class Employee {
    constructor(name, remainingHolidays, dgree, performance){
        this.name=name //名称
        this.remainingHolidays= remainingHolidays // 剩余假期天数
        this.dgree=dgree // 雇员级别 1初级,2中级,3高级
        this.performance=performance // 绩效 1不及格 2及格 3优秀
    }

    accept(visitor){
        visitor.visit(this)
    }
}

class  EmployeeGroup {
    employeeMap=new Map()

    addEmployee(employee){
        this.employeeMap.set(employee.name, employee)
    }

    // 遍历accept
    accept(visitor){
        for(let employee of this.employeeMap.values()){
            employee.accept(visitor)
        }
    }

}
然后我们创建一个抽象访问者对象和具体访问者对象
// 抽象访问者,定义访问的接口
class MoneyVisitor{
    visit(employee){
        throw new Error('need overwrite this function')
    }
}

// 2018年的福利规则
class MoneyVisitorIn2018 extends MoneyVisitor{
    visit(employee){
        const {name,remainingHolidays, dgree, performance} = employee
        const result= 200* remainingHolidays * dgree * performance
        console.log(name, result )
    }
}
当我们需要实现福利转换的时候,我们只需要将具体的visitor对象传入给group,由各个employee对象来执行具体的方法即可
const group = new EmployeeGroup()
group.addEmployee(new Employee('小王',3,1,1))
group.addEmployee(new Employee('小李',1,2,3))

group.accept(new MoneyVisitorIn2018())

// 小王 600
// 小李 1200
这样当我们需要对福利规则进行变化的时候,我们只需要创建新的规则Visitor进行调用即可,不会对employee和group对象的结构进行破坏!

**By the way。**我们常见代码中有哪些访问者模式了?

  1. Js 的call和apply就是访问者模式的精髓,我们可以不去改变执行对象this的结构的同时,为它添加新的操作方法,来实现对操作对象的访问!
  2. vue的Dep和watch的绑定也是访问者模式。dep可以看作是被访问对象,watcher可以看作成访问对象:
  • 在defineReactive中通过dep.depend()建立观察
  • depend方法是找到当前的watcher,然后调用watcher.addDep(this),【hint:可以看成是accept方法】
Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};
  • 然后在wather的addDep方法中是具体对dep对象的访问操作
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

适用场景:

  • 对象数据结构稳定,但是操作的方法易变

优点:

  • 符合单一职责原则

  • 优秀的扩展性,操作方法可以灵活变化

缺点:

  • 具体元素对访问者公布细节,违反了迪米特原则

  • 具体元素变更比较困难。

  • 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

区别:

  • 访问者模式关注对对象的操作,迭代器模式关注对对象的迭代

推荐程度:⭐️⭐️⭐️

解释器模式

**解释器模式:**对于一种语言,给出其文法表示形式,并定义一种解释器,通过使用这种解释器来解释语言中定义的句子。

解释器模式人如其名,非常好理解,可以把语言看作一种DSL,把解释器看作成对DSL的执行器。我们可以把一系列操作按照特定的语言格式进行组装。在需要调用时,对组装的语言进行解析,然后通过解释器完成具体动作的执行。
在开发中常见的例子有SQL解析和执行、JSON Schema实现页面编排。我们以动态表单中的schema为例,当我们需要渲染一个input时,我们组装如下schema
{
    label:'名称',
    field:'name',
    component:'el-input',
    props:{
        clearable:true
    },
    events:{
        change:()=>{}
    }
}
解释器对schema进行处理,按如下规则进行渲染,就可以实现组件的渲染
<el-form-item path={item.field}>
  <template slot="label">
    {item.label}
  </template>

    <renderCompStr
      {...item.props}
      on={item.events}
    >
    </renderCompStr>

</el-form-item>

适用场景:

  • 一些重复出现的问题可以用一种简单的语言来进行表达

  • 语言可以进行抽象和组合

  • 简单语法需要解释的场景

优点:

  • 语言可以按照特定语法进行组合,相对于策略模式,可以处理的场景更加多、更复杂

  • 可扩展性比较好,可以通过增加了新的解释表达式的方式来满足新的操作

缺点:

  • 复杂语言比较难维护

推荐程度:⭐️⭐️⭐️

命令模式

命令模式的定义是:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。

比如你对小狗发出“坐下”的命令,他就会按命令坐下来。在JavaScript中最简单的命令模式就是调用对象的属性方法:
const obj={
    say:()=>{
        console.log('hi')
    },
    eat:()=>{
        console.log('eating...')
    }
}
// 命令模式调用obj的say方法
obj.say()// 命令模式调用obj的say方法
其他常见的例子如Vuex中的mutations、actions:
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }})
我们通过dispatch发出命令,调用action中的increment函数
store.dispatch('increment'1)
然后actions继续commit命令,调用mutations对state进行修改
如果要实现一个比较完善的命令模式,我们通常需要在对象中维护execute执行、undo撤销、redo重做等相关命令方法,以及taskStack任务栈等。

适用场景:

  • 行为的请求者与行为的处理者耦合度过高,需要进行解耦

  • 调用方只关心命令的发送,不关心处理的过程

优点:

  • 调用方和执行方高度解耦

  • 满足开闭原则,可以非常方便的增加新的命令

缺点:

  • 可能会存在过多的命令

推荐程度:⭐️⭐️⭐️⭐️

职责链模式

职责链模式的定义:使多个对象都有机会处理请求,从而避免了请求的发送者与多个接收者直接的耦合关系,将这些接收者连接成一条链,顺着这条链传递该请求,直到找到能处理该请求的对象。

JS开发中常见的职责链模式有express和koa的中间件,可以对request和response进行链式的处理,并且各个处理函数间不耦合!webpack的proxy - bypass也是使用了这种概念,对请求代理的规则进行链式处理。( 此处感谢leto曾经帮我查bypass的bug )
感兴趣的同学可以趁机看一波express和koa的中间件的源码

使用场景:

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

  • 可动态指定一组对象处理请求。

优点:

  • 降低耦合度。它将请求的发送者和接收者解耦。

  • 简化了对象。使得对象不需要知道链的结构。

  • 增加新的请求处理类很方便。

缺点:

  • 不能保证请求一定被接收。

  • 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。

  • 可能不容易观察运行时的特征,有碍于除错。

推荐程度:⭐️⭐️⭐️

思考题

到这里我们已经介绍完所有经典的设计模式,下面想和大家互动讨论一下设计模式的应用。

现在low code和 no code方案逐渐热了起来,大家或多或少使用/看过相关的产品,比如说amis、anole-form、formly等。这些产品都各有一些缺点和局限性。

那如果需要你来设计一套low code方案,结合你现在的需求,你会实现哪些功能模块应用哪些设计模式?面对自己认为麻烦的特性和功能,如何进行设计优化

有想法的同学可以在下方留言自己的设计思路,我也会结合自己在low code上的经验和大家进行讨论。

参考资料

[JavaScript设计模式 - Addy Osmani](https://book.douban.com/subject/24744217/)
[javascript设计模式 - 张容铭](https://book.douban.com/subject/26589719/)
[菜鸟教程-设计模式](https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html)
[java设计模式之访问者模式](https://www.bilibili.com/video/BV1ot411C7vv?p=4)
[设计模式大杂烩](https://www.cnblogs.com/zuoxiaolong/p/pattern26.html)
[Java设计模式总结](https://www.cnblogs.com/pony1223/p/7608955.html)
[阮一峰ES6 Iterator](https://es6.ruanyifeng.com/#docs/iterator)