设计模式

设计模式

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性已经类的关联关系和组合关系的的充分理解。

  1. 单例(Singleton)模式: 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

  2. 原型(Prototype)模式: 将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

  3. 工厂方法(Factory Method)模式: 定义一个用于创建产品的接口,由子类决定生产什么产品。

  4. 抽象工厂(AbstractFactory)模式: 提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

  5. 建造者(Builder)模式: 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

  6. 代理(Proxy)模式: 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

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

  8. 桥接(Bridge)模式: 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

  9. 装饰(Decorator)模式: 动态的给对象增加一些职责,即增加其额外的功能。

  10. 外观(Facade)模式: 为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  11. 享元(Flyweight)模式: 运用共享技术来有效地支持大量细粒度对象的复用。

  12. 组合(Composite)模式: 将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

  13. 模板方法(TemplateMethod)模式: 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

  14. 策略(Strategy)模式: 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

  15. 命令(Command)模式: 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

  16. 职责链(Chain of Responsibility)模式: 把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

  17. 状态(State)模式: 允许一个对象在其内部状态发生改变时改变其行为能力。

  18. 观察者(Observer)模式: 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

  19. 中介者(Mediator)模式: 定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

  20. 迭代器(Iterator)模式: 提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

  21. 访问者(Visitor)模式: 在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

  22. 备忘录(Memento)模式: 在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

  23. 解释器(Interpreter)模式: 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

单例模式

为了节省内存资源,保证数据内容的一致性。

一个类只有一个实例(创建的多个实例子都相等)。

该单例对象必须由单例类自行创建。

单例类对外提供一个访问该单例的全局访问点。

代码演示

1
class SingleCase() {
2
  login() {
3
    console.log('login...')
4
  }
5
}
6
7
SingleCase.getInstance = (function() {
8
  let instance;
9
  return function() {
10
    if(!instance) {
11
      instance = new SingleCase();
12
    }
13
    return instance;
14
  }
15
})();
16
17
const obj1 = SingleCase.getInstance();
18
const obj2 = SingleCase.getInstance();
19
20
obj1 === obj2 // true

应用场景

  • jQuery - 引入多个 jQuery 也只有一个 $
    1
    if(window.jQuery != null) {
    2
      return window.jQuery
    3
    } else {
    4
      // 初始化
    5
    }
  • vuexredux 中的 store

设计原则验证

  • 符合单一职责原则,只实例化唯一的对象
  • 没法具体开放封闭原则,但是绝对不违反开放封闭原则

工厂模式

不对外暴露创建逻辑,对 new 操作进行封装。

代码演示

1
class Product {
2
  constructor(name) {
3
    this.name = name;
4
  }
5
  init() {
6
    console.log(this.name);
7
  }
8
}
9
10
class Creator {
11
  create(name) {
12
    return new Product(name);
13
  }
14
}
15
16
let creator = new Creator();
17
let abc = creator.create('abc');
18
abc.init(); // abc

应用场景

  • jQuery - $('div')
  • React.createElement
  • vue 异步组件。

设计原则验证

  • 工厂函数和创建者分离
  • 符合开放封闭原则

适配器模式

装饰器模式

生活场景中的插座适配。

将一个类(对象)的接口(方法或者属性)转化成另外一个接口以满足用户需求,使类(对象)之间接口不兼容问题通过适配器得以解决。

包含角色:

  • 目标类(Target)- 定义客户需要的接口。
  • 被适配类(Adaptee)- 定义需要适配的接口。
  • 适配器类(Adapter)

代码演示

1
class Adaptee {
2
  // 需要适配
3
  specificRequest() {
4
    return '德国标准的插头'
5
  }
6
}
7
8
class Tagert {
9
  constructor() {
10
    this.adapter = new Adaptee();
11
  }
12
  request() {
13
    let info = this.adapter.specificRequest();
14
    return `${info} -> 转化器 -> 国标插头`
15
  }
16
}
17
18
let target = new Target();
19
target.request();

应用场景

  • 封装旧接口

设计原则验证

  • 将现有接口和使用者进行分离
  • 符合开放封闭原则

装饰器模式

装饰器模式
生活场景中的手机壳。

为对象添加新功能,不改变其原有结构和功能。

代码演示

1
class Circle {
2
  draw() {
3
    console.log('画一个原型') 
4
  }
5
}
6
7
class Decorator {
8
  constructor(circle) {
9
    this.circle = circle;
10
  }
11
  draw() {
12
    this.circle.draw();
13
    this.setColor(circle);
14
  }
15
  setColor(circle) {
16
    console.log('设置颜色')
17
  }
18
}
19
20
let circle = new Circle();
21
circle.draw()
22
23
let decorator = new Decorator(circle);
24
decorator.draw()

应用场景

  • ES7 装饰器

    1
    @decorator
    2
    class A {}
    3
    4
    // 等同于
    5
    6
    class A {}
    7
    A = decorator(A) || A;
  • core-decorators - 第三方开源

设计原则验证

  • 将现有对象和装饰器分离,两者独立存在
  • 符合开放封闭原则

代理模式

代理模式

生活场景:翻墙访问国外网站,商品代理商。

使用者无权访问目标对象,中间加代理,通过代理做授权和控制。

代码演示

1
2
// 目标类
3
class ReadImage {
4
  constructor(fileName) {
5
    this.fileName = fileName;
6
    this.loadFromDisk();
7
  }
8
  display() {
9
    console.log('display...' + this.fileName);
10
  }
11
  loadFromDisk() {
12
    console.log('loading...' + this.fileName);
13
  }
14
}
15
16
// 代理类
17
class ProxyImage {
18
  constructor(fileName) {
19
    this.realImage = new ReadImage(fileName);
20
  }
21
  display() {
22
    this.realImage.display();
23
  }
24
}
25
26
let proxyImage = new ProxyImage('1.png');
27
proxyImage.display();

应用场景

  • 网页事件代理 - addEventListener
  • jQuery - $.proxy
  • ES6 中的 Proxy
    1
    // 明星
    2
    const star = {
    3
      name: '李连杰',
    4
      age: '28',
    5
      phone: '13100000000'
    6
    }
    7
    8
    // 经纪人
    9
    const agentStar = new Proxy(star, {
    10
      get: function(target, key) {
    11
        if(key === 'phone') {
    12
          // 返回经纪人自己的电话
    13
          return '13811111111'
    14
        }
    15
        if(key === 'price') {
    16
          // 明星不报价 经纪人报价 
    17
          return 120000;
    18
        }
    19
        return target[key];
    20
      },
    21
      set: function(taget, key, value) {
    22
        if(key === 'customPrice') {
    23
          if(value < 100000) {
    24
            throw new Error('价格太低,最低10w')
    25
          } else {
    26
            target[key] = value;
    27
            return true;
    28
          }
    29
        }
    30
      }
    31
    })
    32
    33
    // 获取明星信息
    34
    console.log(agentStar.name); // 李连杰
    35
    console.log(agentStar.age); // 28
    36
    console.log(agentStar.phone); // 经纪人的电话 13811111111
    37
    console.log(agentStar.price); // 经纪人报价 120000
    38
    39
    // 报价
    40
    agentStar.customPrice = 150000;
    41
    agentStar.customPrice = 90000; // 报错 价格太低 最少10w

设计原则验证

  • 代理类和目标类分离,隔离开目标类和使用者
  • 符合开放封闭原则

外观模式

外观模式

为子系统中的一组接口提供了一个高层接口,使用者使用这个高层接口。

不符合单一职责原则和开放封闭原则,谨慎使用。

观察者模式

它是一种一对多的关系,让多个观察者(Observer)同时监听一个主题(Subject),这个主题也就是被观察者(Observable),被观察者的状态发生变化时就会通知所有的观察者,使得它们能够接收到更新的内容。

代码实现

1
// 定义一个主题(被观察者)
2
class Subject {
3
  constructor() {
4
    this.observers = []; // 记录观察者
5
    this.state = 0; 
6
  }
7
  getState() {
8
    return this.state;
9
  }
10
  setState(state) {
11
    this.state = state;
12
    this.notify();  // 通知观察者更新了
13
  }
14
  attach(observer) {
15
    this.observers.push(observer); // 对观察者进行登记
16
  }
17
  notify() {
18
    this.observers.forEach(observer => {
19
      observer.update();
20
    })
21
  }
22
}
23
24
// 定义观察者类
25
class Observer {
26
  constructor(name, subject) {
27
    this.name = name;
28
    this.subject = subject; 
29
    this.subject.attach(this); // 向登记处添加观察者实体
30
  }
31
  update() {
32
    console.log(`${this.name} update, state: ${this.subject.getState()}`);
33
  }
34
}
35
36
// 创建一个主题
37
const subject = new Subject();
38
39
// 创建三个观察者
40
const observer$1 = new Observer('A', subject);
41
const observer$2 = new Observer('B', subject);
42
const observer$3 = new Observer('C', subject);
43
44
// 更新主题
45
subject.setState(1);
46
subject.setState(2);
47
48
// "observer$1 update, state: 1"
49
// "observer$2 update, state: 1"
50
// "observer$3 update, state: 1"
51
// "observer$1 update, state: 2"
52
// "observer$2 update, state: 2"
53
// "observer$3 update, state: 2"

更多阅读:观察者模式 vs 发布订阅模式

应用场景

设计原则验证

  • 主题和观察者分离,不是主动触发而是被动监听
  • 符合开放封闭原则

迭代器模式

迭代器(Iterator)模式又叫游标(Sursor)模式,迭代器具有 next() 方法,可以顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表现。

代码演示

1
// 迭代器对象
2
class Iterator {
3
  constructor(container) {
4
    this.list = container.list;
5
    this.index = 0;
6
  }
7
  next() {
8
    if(this.hasNext()) {
9
      return {
10
        value: this.list[this.index++],
11
        done: false
12
      }
13
    }
14
    return {value: null, done: true}
15
  }
16
  hasNext() {
17
    if(this.index >= this.list.length) {
18
      return false;
19
    }
20
    return true;
21
  }
22
  
23
}
24
25
// 目标对象
26
class Container {
27
  constructor(list) {
28
    this.list = list;
29
  }
30
  getIterator() {
31
    return new Iterator(this);
32
  }
33
}
34
35
let container = new Container([1, 2, 3, 4, 5]);
36
let iterator = container.getIterator();
37
38
iterator.next();  // {value: 1, done: false}
39
iterator.next();  // {value: 2, done: false}
40
iterator.next();  // {value: 3, done: false}
41
iterator.next();  // {value: 4, done: false}
42
iterator.next();  // {value: 5, done: false}
43
iterator.next();  // {value: null, done: true}

应用场景

  • jQuery - each()
  • ES6 Iterator

设计原则验证

  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象隔离开
  • 符合开放封闭原则

状态模式

生活场景:交通信号灯不同颜色的变化

对有状态的对象,把复杂的「判断逻辑」提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

代码实现

1
// 状态对象
2
class State {
3
  constructor(color) {
4
    this.color = color;
5
  }
6
  handle(context) {
7
    console.log(`turn to ${this.color} light`);
8
    // 改变行为
9
    context.setState(this);
10
  }
11
}
12
13
// 主题对象
14
class Context {
15
  constructor() {
16
    this.state = null;
17
  }
18
  getState() {
19
    return this.state;
20
  }
21
  setState(state) {
22
    this.state = state;
23
  }
24
}
25
26
let context = new Context();
27
28
let greed = new State('greed');
29
let yellow = new State('yellow');
30
let red = new State('red');
31
32
// 绿灯亮了
33
greed.handle(context);
34
console.log(context.getState())
35
36
// 黄灯亮了
37
yellow.handle(context);
38
console.log(context.getState())
39
40
// 红灯亮了
41
red.handle(context);
42
console.log(context.getState())

应用场景

  • 有限状态机(Promise 就是一个有限状态机)

设计原则验证

  • 将状态对象和主题对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则

其他设计模式

软件设计模式概述