banner
banner
banner
NEWS LETTER

设计模式

Scroll down

设计模式

高内聚,低耦合(五大基本原则SOLID简写)

  • 高层模块不依赖底层模块,即为依赖反转原则。
  • 内部修改关闭,外部扩展开放,即为开放封闭原则。
  • 聚合单一功能,即为单一功能原则。
  • 低知识要求,对外接口简单,即为迪米特法则。
  • 耦合多个接口,不如独立拆分,即为接口隔离原则。
  • 合成复用,子类继承可替换父类,即为里式替换原则

为什么封装代码?

  1. 不会让内部变量污染外部
  2. 封装好的代码可以作为一个模块给外部调用
  3. 对扩展开放,对修改关闭,即开放关闭原则。外部不能修改内部源码,保证了内部的正确性;又留出扩展接口,提高灵活性。

设计模式

  • 在软件设计、开发过程中,针对特定场景、特定问题的较优解决方案。
  • 一共分为3大类23种,下面为常用的几种:
    • 创建型模式:单例模式、工厂模式
    • 结构型模式:适配器模式、装饰器模式
    • 行为型模式:策略模式、观察者模式、发布订阅模式、访问者模式

单例模式

一个类只有一个实例,并提供一个访问它的全局访问点。

  • 实现方法:
    • Singleton: 特定类,需要访问的类
    • instance:单例,特定类的实例
    • getInstance: 获取单例的方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class SingLeton {
      let _instance = null;
      static getInstance(){
      if(!Singleton._instance){
      Singleton.instance = new Singleton()
      }
      return Singleton._instance //存在实例就返回
      }
      }
      const s1 = Singleton.getInstance()
      const s2 = Singleton.getInstance()
      console.log(s1===s2) //true
  • 举例:vuex的store
  • 优点:节约资源,保证访问的一致性。
  • 缺点:扩展性不友好,因为单例模式没有接口
  • 场景:
    • 如果一个类实例化过程消耗资源比较多,可以使用单例避免性能浪费
    • 需要公共状态,可以使用单例保证访问一致性。

工厂模式

根据不同的,返回不同类的实例
核心思想:将对象的创建与对象的实现分离

  • 实现方法:
    • Visitor:访问者,访问共仓的方法
    • Factory;工厂,负责返回产品实例
    • Product:产品,访问者从工厂拿到的产品实例
      1
      2
      3
      4
      5
      6
      7
      export function createElement(tag, data,children){
      //各种判断 判断生成什么样的Vnode
      if(!tag){
      return craeteEmptyVNode()
      }
      return new VNode(tag,data,children)
      }
  • 举例:document.createElement创建DOM元素,createElement(‘标签名’)
  • 优点:良好的封装,扩展性良好,解耦了高层逻辑和底层产品类
  • 缺点:给系统增加了抽象性,带来了额外的系统复杂度,不能滥用
  • 场景:对象创建复杂,访问者无需了解创建过程。需要处理大量又相同、相似属性的小对象

适配器模式

用于解决兼容问题,接口/方法/数据不兼容,将其转换成访问者期望的格式进行使用。

  • 必须包含目标(Target)、源(Adaptee)和适配器(Adapter)三个角色。
  • 优点:可以使原有逻辑得到更好的复用,有助于避免大规模改写现有代码
  • 缺点:会让系统变得零乱,明明调用 A,却被适配到了 B,不能滥用。
  • 场景:
    • 想要使用一个已经存在的对象,但是接口不满足需求
    • 想要创建一个可以复用的对象,而且确定需要和一些不兼容的对象一起工作
  • 使用:前提有多个不同接口的数据
    • 定义一个同意的数据结构
    • 通过适配器模块,适配成访问者索要访问的数据格式
    • 将多个不同接口的数据在这里定义号类型,然后其中的相似数据通过调用一个函数转换成一致的类型,然后适配器哪里每一个接口都遍历数据单独返回给对应接口,最终返回适配器列表。通过打.对应的列表。

装饰器模式

在不改变原对象的基础上,增加新属性、方法、功能(一个对象被另一个对象包装,在原对象上增加功能)

  • 优点:对象的核心职责和装饰器功能去分开,可以通过动态增删装饰目标对象中的装饰逻辑

策略模式

定义一系列算法,根据输入的参数决定使用哪个算法

  • 举例: 双十一满减的活动,表单验证
  • 优点:
    1. 策略相互独立,可以互相切换。
    2. 提高了灵活性以及复用性,维护性。
    3. 可扩展性好,满足开闭原则。
  • 缺点:
    1. 策略相互独立,一些复杂的算法逻辑无法共享,造成资源浪费。
    2. 用户在使用策略时,需要了解具体的策略实现。
    3. 不满足最少知识原则,增加了使用成本。
  • 场景:
    • 算法需要自由切换的场景。
    • 多个算法只有行为上有些不同,可以考虑策略模式动态选择算法。
    • 需要多重判断,可以考虑策略模式规避多重条件判断。
1
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
const priceCalculate = (function(){
const discountMap = {
'discount200-20': function(price) {
return price - Math.floor(price / 200) * 20;
},
'discount300-50': function(price) {
return price - Math.floor(price/300) * 50;
},
'discount500-100': function(price) {
return price - Math.floor(price/500) * 100;
},
};
return {
addStategy(stategyName,fn){
if(discountMap[stategyName]) return;
discountMap[stategyName] = fn;
},
priceCal(discountType,price){
return discountMap[discountType] && discountMap[discountType](price);
}
}
})()
// 使用
priceCalculate.priceCal('discount200-20',250); // 230
priceCalculate.addStategy('discount800-200',function(price){
return price - Math.floor(price/800) * 200;
})

观察者模式

一个对象(称为subject)维持一系列依赖于它的对象(称为observer),将有关状态的任何变更自动通知给它们(观察者)。

  • 观察者模式中的角色有两类:观察者和被观察者。
  • 优点:目标变化就会通知观察者,这是观察者模式最大的优点。
  • 缺点:不灵活。目标和观察者是耦合在一起的,要实现观察者模式,必须同时引入被观察者和观察者才能达到响应式的效果。
  • 实现:被观察者的类要添加/移除/通知观察者;观察者的类中要根据被观察者更新

发布-订阅模式

基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知。

  • 发布-订阅模式的角色有两类:发布者和订阅者
1
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
class Event {
constructor() {
this.eventEmitter = {};
}
// 订阅
on(type, fn) {
if (!this.eventEmitter[type]) {
this.eventEmitter[type] = [];
}
this.eventEmitter[type].push(fn);
}
// 取消订阅
off(type, fn) {
if (!this.eventEmitter[type]) {
return;
}
this.eventEmitter[type] = this.eventEmitter[type].filter((event) => {
return event !== fn;
});
}
// 发布
emit(type) {
if (!this.eventEmitter[type]) {
return;
}
this.eventEmitter[type].forEach((event) => {
event();
});
}
}
  • Vue 双向绑定通过数据劫持和发布-订阅模式实现。
    1. 通过DefineProperty劫持各个数据的setter和getter,并为每个数据添加一个订阅者列表,这个列表将会记录所有依赖这个数据的组件。响应式后的数据相当于消息的发布者。
    2. 每个组件都对应一个Watcher订阅者,当组件渲染函数执行时,会将本组件的Watcher加入到所依赖的响应式数据的订阅者列表中。相当于完成了一次订阅,这个过程叫做“依赖收集”。
    3. 当响应式数据发生变化时,会出setter,setter负责通知数据的订阅者列表中的Watcher,Watcher触发组件重新渲染来更新视图。视图层相当于消息的订阅者。

发布订阅者与观察者的区别

观察者模式与发布订阅者模式的区别

  • 观察者模式:
    • 媒介: 观察者 –> 被观察者(一有变化就通知被观察者)
    • 方式: 同步,即当发生某些事件时,主题调用其所有观察者的适当方法3
    • 松耦合: 观察者和被观察者松散耦合
  • 发布订阅者模式:
    • 媒介: 发布者 –> 中间代理 –> 订阅者 (通过中间代理通信)
    • 方式: 异步(使用消息队列)
    • 松耦合: 组件与观察者模式完全分离
    • 模式: 更像是一种跨应用程序模式,可以驻留在两个不同的应用程序中。每一个都通过消息代理或消息队列进行通信。

EventEmitter

  • 优点:
  1. 时间解耦:注册的订阅行为由发布者决定何时调用,订阅者无需持续关注,由发布者负责通知。
  2. 对象解耦:发布者无需知道消息的接受者,只需遍历订阅该消息类型的订阅者发送消息,解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象。
  • 缺点:
  1. 资源消耗:创建订阅者需要一定的时间和内存。
  2. 增加复杂度:弱化了联系,难以维护调用关系,增加了理解成本。
  • 场景:各模块相互独立,存在一对多的依赖关系,依赖模块不稳定、依赖关系不稳定,各模块由不同的人员开发

访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于其内部各个元素的新操作。访问者模式是一种将数据操作和数据结构分离的设计模式

  • 场景:
    • 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
    • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
  • 实现:在每个员工的类中添加一个public void accept(Visitor visitor){visitor.visit(this)}和各自对应的方法,在员工的基类中定义好共同的方法和核心方法public abstract void accept(Visitor visitor),然后汇集到员工业务报表,并为访问者提供接口。定义Visitor的方法,访问员工,然后领导可以根据自己要访问的要求找到对应的数据。
    • Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
    • ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
    • Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
    • ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
    • ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
  • 优点:
  1. 各角色责职分离,符合单一职责原则
  2. 具有优秀的扩展性
  3. 灵活性
  4. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
  • 缺点:
  1. 具体元素对访问者公布细节,违反了迪米特原则
  2. 具体元素变更时导致修改成本大
  3. 违反了依赖倒置原则,位类达到“区别对待”而依赖了具体类,没有依赖抽象
    UML设计图
其他文章
cover
Mobx库
  • 24/11/01
  • 16:52
  • 前端工程化工具
目录导航 置顶
  1. 1. 高内聚,低耦合(五大基本原则SOLID简写)
  2. 2. 为什么封装代码?
  3. 3. 设计模式
  4. 4. 单例模式
  5. 5. 工厂模式
  6. 6. 适配器模式
  7. 7. 装饰器模式
  8. 8. 策略模式
  9. 9. 观察者模式
  10. 10. 发布-订阅模式
  11. 11. 观察者模式与发布订阅者模式的区别
  12. 12. EventEmitter
  13. 13. 访问者模式
请输入关键词进行搜索