设计模式

设计模式目录:22种设计模式open in new window

设计原则

软件模式的产生是因为变化的东西太多,为减轻人类的负担, 将一些不变的东西先用模式固化,这样让人类可以更加集中精力对付变化的东西,所以 在软 件中大量反复使用模式(我个人认为这样的软件就叫框架软件了,比如 J2EE),不但没阻碍软 件的发展,反而是推动了软件的发展.因为其 他使用这套软件的人就可以将更多精力集中在 对付那些无法用模式的应用上来.

image.png

基于六大设计原则:

  • Single 单一职责 如写函数、职责过多
  • Open 开放封闭 对扩展开放,对修改封闭
  • Liskov 里氏替换 程序里的对象都应该可以被它的子类实例替换而不用更改程序.
  • Law of Demeter 迪米特法则 最少知识 直接交流、减少耦合
  • Interface 接口隔离 尽可能小的接口, 多个专用的接口比一个通用接口好。
  • Dependency 依赖倒置 面向接口而非面向实现类,高层模块依赖于抽象而非细节 反向依赖

里氏替换

可替代性(指父类?)

讲继承复用,对开闭原则的补充

子类可以扩展父类的功能,但不能改变父类原有的功能

发布订阅

  • 订阅者,数据结构是事件名到事件回调列表的映射,有一个默认事件any
  • 两个方法,一个是接收,参数是回调和事件名,一个是发出,参数是消息和事件名
    • 接收, 就是写入订阅表,注意事件名存在时,回调列表要 push
    • 发出,遍历调用事件名下的回调
  • 数据驱动,数据与逻辑分离,前因后果
  • 扩展:MVC,MVVM
class Event {
  constructor() {
    this.cacheList = new Map()
  }

  on(type, fn) {
    if (!this.cacheList.get(type)) {
      this.cacheList.set(type, [fn]) // 注意这里设置的值是数组
    } else {
      this.cacheList.get(type).push(fn)
    }
  }

  emit(type, data) {
    if (!this.cacheList.get(type)) throw 'event not found'
    for (let fn of this.cacheList.get(type)) {
      fn(data)
    }
  }
}

let event = new Event()

event.on('click', data => console.log(`event data: ${data}`))
event.emit('click', 'hello') // event data: hello

ES6 语法实践,用 ES6 重写《JavaScript Patterns》中的设计模式 - CNode 技术社区open in new window

与观察者区别

  • 是否耦合

观察者模式:

观察者模式是一种对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。观察者模式中,被观察者对象(也称为主题)维护一个观察者列表,并在状态发生变化时通知所有的观察者对象。

观察者模式的应用场景包括:用户界面组件、事件处理、模型-视图-控制器(MVC)模式等。

发布-订阅模式:

发布-订阅模式是一种消息传递机制,它定义了一种松耦合的方式来处理对象之间的通信。在发布-订阅模式中,发布者(也称为生产者)不会直接将消息发送给订阅者(也称为消费者),而是将消息发送给消息中心(也称为主题),由消息中心将消息广播给所有订阅该消息的订阅者。

发布-订阅模式的应用场景包括:事件驱动编程、异步编程、消息队列等。

迭代器模式

[[iterator-pattern]]

策略模式

算法实现和使用分离,策略类可互换,环境类接受用户请求,将请求委托给策略类

将不变的部分和变化的部分分隔开来是每个设计模式的主题。

至少两部分组成: 一是策略类,策略类封装了具体的算法,并负责具体的算法。 二是环境,类 Context 接受客户的请求,随后把请求委托给某一个策略类。

// 计算工资
var strategies = {
  S: function(salary) {
    return salary * 4
  },
  A: function(salary) {
    return salary * 5
  },
  B: function(salary) {
    return salary * 6
  },
}

var calculateBonus = function(level, salary) {
  return strategies[level](salary) // Context
}

console.log(calculateBonus('A', 5000))
console.log(calculateBonus('S', 5000))

工厂方法

将创建实例的责任与使用实例的责任分开

拿到商品,不需要关心怎么生产

单例

单一实例对象 对外提供全局访问

保证一个类只有一个实例,唯一性、一致性、性能

创建对象,管理单例两个职责分离

建造器

将复杂对象的创建逻辑与最终表现分离 JS 中很少用

适配器

通过对象包装,解决接口数据结构不匹配,不改变已有接口,实现协同

代理

为对象提供一个代用品或占位符,以便控制对它的访问

  • 场景:
  • 优点:
  • 分类:
    1. 虚拟代理,把开销大的对象,延迟到真正需要时创建。 比较常用,如实现图片预加载,对象 A 负责创建 img 标签、设置 src,代理对象 B 设置 loading 图,监听 onload 后调用 A
    2. 保护代理,权限控制

合成复用原则

装饰器、包装

decorator/wrapper

在不改变元对象的基础上,对对象进行包装和拓展

将不同职责的代码装饰合并 不改变原有代码,直接修改原函数违返开闭原则

跟代理像,但代理是不方便访问本体,装饰是不确定全部功能

应用:

  • 分离业务代码和数据统计代码 Function.after
  • 分离表单校验和合并 Function.before

享元

flyweight

内部状态相同、共享 对象太多,用时间换空间的性能优化

模板方法

两部分:抽象父类和具体实现子类,父类封装子类的算法框架,子类继承

从多个子类中,分享共同点 轮廓,骨架,通用,穷举

命令模式

可撤销,并发,程序无状态

职责链

使多个对象都有机会处理请求,发送、多个接收解耦,传给第一个接收即可

应用:

  • 多种支付场景,优惠券,库存

notes copy

  1. 设计模式是一门语言加速程序员之间的沟通效率

  2. 设计模式为了解决特定问题每个设计模式都有优缺点,使用就要付出缺点的代价 a. Eg 观察者模式 i. 优点

    1. 解耦 ii. 缺点
    2. 观察者模式会导致代码可读性和维护性下降
  3. 设计模式只做一件事 a. 控制逻辑集中化

  4. 设计模式分为三种 a. 创建 i. 目的为了创建对象 ii. 创建对象配置化,本身就是满足了设计模式的精髓,目的是灵活 b. 结构

    1. 处理类或对象的组合
    2. 处理功能的组合关系 c. 行为
    3. 描述类和对象怎么交互和怎样分配职责
    4. 数据和数据处理分离
    1. 任何的代码都是 a) 数据结构 b) 算法逻辑
  5. 重点记忆 a. 创建型

    1. 工厂
    2. 建造者
    3. 单例 b. 结构型
    4. 适配器
    5. 装饰
    6. 享元
    7. 代理 c. 行为型
    8. 责任连
    9. 命令
    10. 观察者
    11. 策略
    12. 访问者
    13. 模板方法
  6. 工厂方法 a. 简单工厂

    1. 控制集中化,可以只维护一个地方的代码 b. 工厂方法的缺点
    2. 开闭原则 c. 解决问题的思路
    3. 工厂方法输出接口
  7. 建造者模式 a. 组合的过程配置化 b. 是一步一步创建一个复杂对象 c. 将一个复杂的对象构建与他的表示分离 d. 创建逻辑集中化

  8. 单例 a. 作用 1. 定义一个全局变量可以确保对象 2. 可以随时都可以被访问 3. 但不能防止我们的实例化多个对象 b. 单例的核心是 1. 配置核心化,配置集中 2. 解决系统控制粒度的核心化

  9. 适配器 a. 一个接口转换成另一个兼容接口 b. 使用场景 1. 第三方系统对接 2. 隔离外部变化时 c. 本质 1. 控制逻辑集中化、变化逻辑集中化 2. 就是一个变化的处理函数 3. 把变化的处理成使用不可变的数据 d. 引申 1. 生态系统稳定

  10. 代理模式 查一下代理模式的常用场景,具体优点

前端的设计模式系列-基本原则 | 前端的设计模式系列open in new windowPatterns.dev - Modern Web App Design Patternsopen in new window

代码局部性(code locality)

理解为代码临近组织,相关逻辑放到一个函数,一个文件,一个包

通常指的是在软件程序中将相关的代码组件放在一起的概念。这种做法旨在改善代码的可读性、可维护性和整体开发效率。通过将相关的代码元素放在一起,开发人员可以更容易地理解和处理逻辑,减少在代码库中导航和理解所需的认知负担。

比如前端 store 相关处理,避免父子组件同时访问和调用,尽量在容器组件处理

状态集中管理和受控组件模式(始终反映外部数据源的状态,而不是多数据源,数据源收敛)

单一职责原则,UI组件的主要责任应该是渲染界面和处理用户交互,而不应该直接管理应用的状态。通过直接访问store,UI组件可能变得过于复杂,同时承担了与状态管理相关的职责,违反了单一职责原则。

数据封装原则 (Encapsulation): 直接访问store可能导致组件对store中数据的依赖性过高,破坏了数据的封装性。组件应该尽量只关心自己的数据和逻辑,而不应该直接操作全局状态。

解耦原则 (Decoupling): 直接访问store会导致组件与特定的状态管理工具(如Vuex或Redux)紧密耦合。这使得将来更换状态管理库或者使用不同的状态管理策略变得更加困难。

Code Locality and the Ability To Navigate – Martin Vysny – First Principles Thinkingopen in new window

Last Updated:
Contributors: gongyi01