快速记住23种设计模式

  • Post author:
  • Post category:其他


设计模式主要分三个类型:创建型、结构型和行为型。

签合同,要分三步:1.创建新建合同 2.设计合同内容结构 3.要遵守合同的行为规范(对应创建,结构,行为三部分)

在这里插入图片描述

口诀:

1.单原二厂建

2.桥(帮)主享外带装适

3.观模职命状

4.(刘)备的爹在房中洁厕

翻译:

1.单原二厂建 (创建类的5个模式,二厂是指简单工厂和抽象工厂)

2.桥(帮)组享外带装适 (结构类的7个模式)

3.观摩(模)职命状

4.刘备的爹(迭代)在房(访)中洁厕(解策)

**父类、构造函数、被依赖者

子类、继承类、派生类、依赖者**

一创建型有:

1、Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点

应用场景:

防抖节流中

命名空间

2、Prototype,原型模式,就是创建一个共享的原型,通过拷贝这个原型来创建新的类,用于创建重复的对象,带来性能上的提升。

var pt = {
 getName: function(){
  return this.first+''+this.last
 },
 say:function(){
  console.log('hello')
 }
}
var x = Object.create(pt)
x.first = 'a'
pt.last = 'b'
x.getName()
//"ab"


var y = Object.create(pt)
y.first = 'ay'
y.last = 'by'
y.getName()
//"ayby"

3、Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使 一个类的实例化延迟到了子类。

4、Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。

场景:solidity中抽象合约,interface接口

function Employee(name) {
    this.name = name;

    this.say = function () {
        console.log("I am employee " + name);
    };
}

function EmployeeFactory() {

    this.create = function (name) {
        return new Employee(name);
    };
}

function Vendor(name) {
    this.name = name;

    this.say = function () {
        console.log("I am vendor " + name);
    };
}

function VendorFactory() {

    this.create = function (name) {
        return new Vendor(name);
    };
}

function run() {
    var persons = [];
    var employeeFactory = new EmployeeFactory();
    var vendorFactory = new VendorFactory();

    persons.push(employeeFactory.create("Joan DiSilva"));
    persons.push(employeeFactory.create("Tim O'Neill"));
    persons.push(vendorFactory.create("Gerald Watson"));
    persons.push(vendorFactory.create("Nicole McNight"));

    for (var i = 0, len = persons.length; i < len; i++) {
        persons[i].say();
    }
}

4、Builder,建造模式:侧重的是过程。

将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。

建造者模式实际,就是一个指挥者,一个建造者和一个用户。用户调用指挥者,指挥者调用具体建造者工作,建造者建造出具体的东西给用户。


二、结构型有:

6、Bridge,桥模式:

  • 将抽象部分与它的实现部分相分离,使他们可以独立的变化。
  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
  • 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  • 桥梁模式的用意是”将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。这句话有三个关键词,也就是抽象化、实现化和脱耦。
var Gestures = function (output) { //abstraction
    this.output = output;

    this.tap = function () { this.output.click(); }
    this.swipe = function () { this.output.move(); }
    this.pan = function () { this.output.drag(); }
    this.pinch = function () { this.output.zoom(); }
};

var Screen = function () {//Concrete
    this.click = function () { console.log("Screen select"); }
    this.move = function () { console.log("Screen move"); }
    this.drag = function () { console.log("Screen drag"); }
    this.zoom = function () { console.log("Screen zoom in"); }
};a
var screen = new Screen();
var hand = new Gestures(screen);
hand.tap();

JavaScript 中桥接模式的典型应用是:Array对象上的forEach函数。

此函数负责循环遍历数组每个元素,是抽象部分;

而回调函数callback就是具体实现部分。

例1:

let forEach = (arr, callbak) => {
    if (Object.prototype.toString.call(callbak) != "[object Function]" || !Array.isArray(arr)) 
        return;
    for (let i = 0; i < arr.length; i++) {
        callbak(arr[i], i);
    }
}

let arr = [1, 2, 3];
forEach(arr, (v, i) => {
    arr[i] *= 2;
})

例2:

在这里插入图片描述

在这里插入图片描述

例3:假设我们要开发一个弹窗插件,弹窗有不同的类型:普通消息提醒,错误提醒,每一种提醒的展示方式还都不一样。这是一个典型的多维度变化的场景。首先我们定义两个类:普通消息弹窗和错误消息弹窗。

这两个类就是前面提到的抽象部分,也就是扩充抽象类,它们都包含一个成员animation。

两种弹窗通过show方法进行显示,但是显示的动画效果不同。我们定义两种显示的效果类如下:

// 2种弹框
function MessageDialog(animation) {
  this.animation = animation;
}
MessageDialog.prototype.show = function () {
  this.animation.show();
}
function ErrorDialog(animation) {
  this.animation = animation;
}
ErrorDialog.prototype.show = function () {
  this.animation.show();
}

// 2种动画形式
function LinerAnimation() {
}
LinerAnimation.prototype.show = function () {
  console.log("it is liner");
}
function EaseAnimation() {
}
EaseAnimation.prototype.show = function () {
  console.log("it is ease");
}

// 调用
var message = new MessageDialog(new LinerAnimation());
message.show();
var error = new ErrorDialog(new EaseAnimation());
error.show();

7、Composite,组合模式:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使 用具有一致性。

应用场景,生成虚拟dom的vnode结构(ST)

将对象组合成树形结构以表示”部分-整体”的层次结构

组合模式使得用户对单个对象和组合对象的使用具有一致性

无须关心对象有多少层,调用时只需在根部进行调

在这里插入图片描述

在这里插入图片描述

8、Flyweight,享元模式。

类似委托模式

共享元数据,侧重考虑内存而非效率。

享元模式可以简单的理解为 单例模式 + 工厂模式 + 管理器 , 管理器对外部状态进行管理组合成完整的对象

运行共享技术有效地支持大量细粒度的对象,避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类)。

在这里插入图片描述

9、Facade,外观模式:为子系统中的一组接口提供一致的界面,facade提供了一高层接口,这个接口使得子系统更容易使用。 (迪米特法则,最少知道原则)

缺点:不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用。(出现胖接口)

与策略模式不同:外观者强调封装多个类的多个任务,策略强调类自己的任务

在这里插入图片描述

A和B都要实现送花,送巧克力的方法,那么我可以通过一个抽象类C实现送花送巧克力的方法(A和B都继承C)。(封装了A,B子类)

var Mortgage = function (name) {
    this.name = name;
}

Mortgage.prototype = {


    applyFor: function (amount) {
        // access multiple subsystems...
        var result = "approved";
        if (!new Bank().verify(this.name, amount)) {
            result = "denied";
        } else if (!new Credit().get(this.name)) {
            result = "denied";
        } else if (!new Background().check(this.name)) {
            result = "denied";
        }
        return this.name + " has been " + result +
            " for a " + amount + " mortgage";
    }
}

var Bank = function () {
    this.verify = function (name, amount) {
        // complex logic ...
        return true;
    }
}

var Credit = function () {
    this.get = function (name) {
        // complex logic ...
        return true;
    }
}

var Background = function () {
    this.check = function (name) {
        // complex logic ...
        return true;
    }
}

function run() {
    var mortgage = new Mortgage("Joan Templeton");
    var result = mortgage.applyFor("$100,000");

    console.log(result);
}

10、Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访问

10.1vue3都 proxy(obj,index,{handle})

10.2虚拟代理:防抖节流、nextTick()

多次请求合并为一共,缓存加载dom

10.3缓存代理

computed

11、Decorator,装饰模式:动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加 灵活。

es7的修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。类是不会提升的,所以就没有这方面的问题。


参考资料


允许向一个现有的对象添加新的功能,同时又不改变其结构

应用场景:接手别人代码,类似继承

solidity中 modify修改器

class SuperMan extends Man {
  fly() {
    console.info('I can fly.')
  }
  newAbility() {
    super.run()
    this.fly()
  }
}

const superMan = new SuperMan()
superMan.run()

@testable 
class MyTestableClass {
  // ..
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable; // true

vue的mixins

function mixins(...list) {
    return function (target) {
        Object.assign(target.prototype,...list); //给target的添加上一个或多个属性或方法
    }
}


const Foo = {
    foo(){
        console.log("foo");
    }
};

@mixins(Foo) //给MyClass 这个类添加了Foo的所有属性和方法
class MyClass {
    
}

let obj = new MyClass();
obj.foo();//控制台输出

第三方开源lib: core-decorators

提供常用的装饰器

装饰和适配器区别:

装饰新增加功能。适配器不增加功能,但是修改原接口。

12、Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工 作那些类可以一起工作。

应用场景:elementUI二次包装开发

class Adaptee {
  sRequest(){
    return '德国插头'
  }
}
class Target {
  constructor(){
    this.adaptee = new Adaptee()
  }
  tRequest (){
    let info = this.adaptee.sRequest()
    return info+'变成中国插头'  // 核心句子
  }
}
let target = new Target()
target.tRequest()

三、行为型有:

13、Memento,备忘录模式:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

举例:游戏死亡后,恢复保存记录状态

14、Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。

举例:foreach、map、filter、reduce、some

var each = function(arr,callback){
 for(var i=0,l=arr.length;i<l; i++){
   callback(i, arr[i])
 }
}
each([1,2,3,4], (x,y)=>{console.log(x,y)})

15、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素 的新操作。

call 和 apply 的作用就是更改函数执行时的作用域,这正是访问者模式的精髓。通过 call、apply 这两种方式我们就可以让某个对象在其它作用域中运行。


参考文章连接

// 访问者
function Visitor() {
  this.visit = function( concreteElement ) {
    concreteElement.doSomething();
  }
}
// 元素类
function ConceteElement() {
  this.doSomething = function() {
    console.log("这是一个具体元素");
  }
  this.accept = function( visitor ) {
    visitor.visit(this);
  }
}
// Client
var ele = new ConceteElement();
var v = new Visitor();
ele.accept( v );

总结

访问者模式解决了数据与数据的操作方法之间的耦合,让数据的操作方法独立于数据,使其可以自由演变。因此,访问者模式更适合于那些数据稳定、但数据的操作方法易变的环境下。

当操作环境改变时,可以自由修改数据的操作方法,实现操作方法的拓展,以适应新的操作环境,而无须修改原数据。如此,对于同一个数据,它可以被多个访问对象所访问,这极大地增加了数据操作的灵活性。

16、Mediator,中介者模式:用一个中介对象封装一些列的对象交互。

中介者模式(Mediator)是用来降低多个对象和类之间的通信复杂性。这种模式提供一个中介类,该类通常处理不同类的通信,并支持松耦合,使代码易于维护。

var mediator = (function () {
    var topics = {}

	// 订阅一个 topic,提供一个回调函数,一旦 topic 被广播就执行该回调
    var subscribe = function(topic, fn) {
        if (!topics[topic]) {
            topics[topic] = []
        }
        topics[topic].push({
            context: this,
            callback: fn
        })

        return this
    }

	// 发布/广播事件到程序的剩余部分
    var publish = function(topic) {
        var args

        if (!topics[topic]) {
            return false
        }

        args = Array.prototype.slice.call(arguments, 1)

        for (var i = 0, l = topics[topic].length; i < l; i++) {
            var subscription = topics[topic][i]
            subscription.callback.apply(subscription.context, args)
        }
        return this
    }

    return {
        publish: publish,
        subscribe: subscribe
    }
})()

我们举个生活中常见的例子——群聊。

这个例子中,群就是一个中介者 Mediator,而小A、小B是 Collegue,他们都是通过群来通信的。

应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

试想如果没有群这个中介者,每个人就通过私聊来互相沟通,那么就需要不断切换聊天窗口,很麻烦。而且这样会形成一个网状的结构。而有了中介者,大家的沟通就会变得很容易,都在群里发送和查看消息。这样网状结构就会变成星状结构。

在这里插入图片描述

// 小A加入了前端群,如果群里有人说话,自己就会收到消息
mediator.subscribe('fe-group', function(data) {
    console.log(data)
})

// 小B也加入了前端群,如果群里有人说话,自己就会收到消息
mediator.subscribe('fe-group', function(data) {
    console.log(data)
})

// 这个时候小B在群里给小A发送了一条信息
// 在群里的人都会收到这条信息
// 打印两次 {message: 'hello A', from: 'B', to: 'A'}
mediator.publish('fe-group', {message: 'hello A', from: 'B', to: 'A'})

17、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言 中的句子。

和组合模式相反。

18、Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们 的客户。

在有多种算法相似的情况下,使用 if…else、switch、卫语句 所带来的复杂和难以维护。

const S = function(salary) {
  return salary * 4
}

const A = function(salary) {
  return salary * 3
}

const B = function(salary) {
  return salary * 2
}

const calculateBonus = function(func, salary) {
  return func(salary)
}

calculateBonus(A, 10000) // 30000
// 未使用策略模式
const formatDemandItemType = (value) => {
    switch (value) {
        case 1:
            return  '初级'
        case 2:
            return  '中级'
        case 3:
            return  '高级'
    }
}


// 策略模式
const formatDemandItemType2 = (value) => { // 用形参匹配
    const obj = {
        1:  '初级' ,
        2:  '中级' ,
        3:  '高级' ,
    }
    
    return obj[value]
}
console.log(formatDemandItemType2(1)) // 输出初级

如下图,一个类中ifelse太多,策略太多,不符合设计模式职能单一原则,所以把每个if拆分成一个类

在这里插入图片描述

在这里插入图片描述

19、Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。

现实中,当校长或者老师在台上发布一个命令的时候,学生收到命令就开始执行。

场景1:btn.addEventListener(‘click’, onClick);

场景2:nodejs监听

var EventEmitter = require('events').EventEmitter; 
var event = new EventEmitter(); 
event.on('some_event', function() { 
    console.log('some_event 事件触发'); 
}); 
    event.emit('some_event'); 

场景3:

 // 创建对象
var targetObj = {
    name:'小李'
}
var targetObj2 = {
    name:'小李'
}
// 定义值改变时的处理函数(观察者)
function observer(oldVal, newVal) {
    // 其他处理逻辑...
    targetObj2.name = newVal
    console.info('targetObj2的name属性的值改变为 ' + newVal);
}
// 定义name属性及其set和get方法(name属性为被观察者)
Object.defineProperty(targetObj, 'name', {
    enumerable: true,
    configurable: true,
    get: function() {
        return name;
    },
    set: function(val) {
        //调用处理函数
        observer(name, val)
        name = val
    }
});
targetObj.name = '张三';
targetObj.name = '李四';
console.log(targetObj2.name)
class AsyncFunArr {
 constructor (...arr) {
  this.funcArr = [...arr]
 }
 next () {
  const fn = this.funcArr.shift()
  if (typeof fn === 'function') fn()
 }
 run () {
  this.next()
 }
}
//首先将fn1,fn2,fn3订阅
const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)
//fn1,fn2,fn3作为分布者分别调用其next()方法:
function fn1 () {
 console.log('Function 1')
 asyncFunArr.next()
}
function fn2 () {
 setTimeout(() => {
  console.log('Function 2')
  asyncFunArr.next()
 }, 500)
}
function fn3 () {
 console.log('Function 3')
 asyncFunArr.next()
}

nodejs中的事件也是观察者模式

生产者-消费者模式和发布-订阅模式属于观察者模式类似的

20、Template Method,模板方法:定义一套流程模板,根据需要实现模板中的操作

例1:vue中{ data(){}, methods{}, watch{}}

例2:

在这里插入图片描述

21、China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的 耦合关系

在这里插入图片描述

打印出:

22、Command,命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及 支持可撤销的操作。

分傻瓜命令模式

一般是3个类,命令者,传达者,接受者

在这里插入图片描述

参考:http://events.jianshu.io/p/4786d1f0e446

var button1 = document.getElementById( 'button1' );
var bindClick = function( button, func ){
    button.onclick = func;
};
var MenuBar = {
    refresh: function(){
        console.log( '刷新菜单界面' );
    }
};
bindClick( button1, MenuBar.refresh );
var bindClick = function(button,func){
    button.onclick = func;
};

var MenuBar = {
    refresh:function(){
        console.log('刷新菜单界面');
    }
};

var SubMenu = {
    add:function(){
        console.log('增加子菜单');
    },
    del:function(){
        console.log('删除子菜单');
    }
};

bindClick(button1,MenuBar.refresh);
bindClick(button2,SubMenu.add);
bindClick(button3,SubMenu.del);

声命(生命)

jquery也属于命令编程

vue属于声明式编程

23、State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。

类似于策略模式。不同的是策略是一个对象的多个行为。状态是多个对象的不同时间的行为。

状态模式维度更高一级。

状态模式和策略模式像一对双胞胎,它们都封装了一系列的算法或者行为,它们的类图看起来几乎一模一样,但在意图上有很大不同,因此它们是两种迥然不同的模式。

外观模式,中间封装了一个接口。判断条件多,容易形成胖接口。

【相同点】:它们都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。

【区别】:策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在。

电灯换挡:开、关、灯光强弱等状态。

文件上传:扫描、正在上传、暂停、上传成功、上传失败等状态。

组件开发中的状态模式——导航:显示、隐藏。

经典示例——红绿灯


按照相似度划分:

一、中介者模式 代理模式 外观模式三者的区别与联系

1,中介者模式:A,B之间的对话通过C来传达。A,B可以互相不认识(减少了A和B对象间的耦合)

2,代理模式:A要送B礼物,A,B互相不认识,那么A可以找C来帮它实现送礼物的愿望(封装了A对象)

3,外观模式和代理模式区别

外观模式不符合设计模式原则,单一职能,开发封闭原则,因为给多个子类提供不同的情况

// 其实我们日常最常用的就是外观模式。我们的工具类,jquery,包括一些浏览器兼容,我们都会把他们封装到一个对象里。

// 这就是外观模式提倡的把复杂的操作封装到一个简单接口中。几乎所有的涉及多个业务对象交互的场景都可以考虑使用外观模式进行重构。

// 外观模式总结:

// 优点:

// * 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并且提升使用便捷度。

// * 实现了客户端与子系统之间的松耦合关系,这使得子系统的变化不会影响客户端。

// 缺点:

// * 不能姮好的限制客户端直接使用子系统类

// * 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开关原则

代理模式和外观者模式这两种模式主要不同就是代理模式针对的是单个对象,而外观模式针对的是所有子类。

代理模式为一个对象提供一种代理以控制对该对象的访问,强调的是对本体对象的访问控制;装饰模式能够动态地为本体对象叠加新的行为,强调的是为本体对象添加新的功能;外观模式为子系统提供了一个更高层次的对外统一接口,强调的是分层和解耦;享元模式通过共享对象来降低系统的资源消耗,强调的是如何在多个对象中共享相同的状态。

二、工厂模式、模板方法模式、建造者模式区别

工厂模式注重的是整体对象的创建方法,而建造者模式注重的是对象的创建过程,创建对象的过程方法可以在创建时自由调用。

四、简单工厂 工厂模式 抽象工厂 区别

简单工厂就是用来生成单一实例的,而工厂模式是可以根据输入输出不同的实例,抽象工厂则是根据不同的工厂生成不同产品的实例。

抽象工厂不直接创建实例,个人认为在大型项目的话应该蛮实用的。

五、观察者模式和发布订阅模式

六、桥接模式、组合模式、适配器模式区别

桥接模式 强调平行级别上,不同类的组合

组合模式 强调部分和整体间的组合

在这里插入图片描述

七、优化审批流(责任链模式结合建造者模式)

js中的链式操作

职责链模式 和 业务结合较多,JS中能联想到链式操作

jQuery的链式操作 Promise.then 的链式操作

设计原则验证

发起者与各个处理者进行隔离

符合开放封闭原则

模板方法多步合并成一步,方便调用

八、享元和单例的区别

享元设计模式是一个类有很多对象,而单例是一个类仅一个对象

九、原型模式和单例模式

原型拔一根猴毛,吹出千万个。单例和原型是互斥的 保证独一无二

十、建造者

建造者侧重过程

十一、外观模式、代理模式、装饰器模式和适配器模式区别

a送花给b,但是a不好意思,a找快递小哥c帮忙送花

1.外观模式(迪米特法则,最少知道原则):a根据b的喜好,拆分送花细节。比如:蓝色的玫瑰周一送,红色的玫瑰周日送。

2.代理模式 :c不做任何处理就是代理模式。

3.装饰器模式:c觉得要帮a一下,就在鲜花上装饰个小串灯、卡片等。程序员修改别人代码常用模式。

4.适配器模式:c觉得b对鲜花过敏,不适合收花,c就把花卖调,给b送了一瓶香水或者衣服。

适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。

装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。


参考资料

十二、发布订阅



版权声明:本文为hkduan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。