详解前端框架中的设计模式,并对比分析优缺点以及使用案例|青训营

137 阅读6分钟

前端框架中,设计模式是一种解决常见问题的经验总结和行之有效的方法。设计模式帮助开发者构建可维护、可扩展、可复用的代码,并提高开发效率。 由于篇幅所限,本文仅介绍几种常见设计模式(前端框架以vue为例)。

1.单例模式


定义:一个类只有一个实例,且该类能自行创建这个实例的一种模式。

优点:确保一个类只有一个实例,节约了系统资源。

缺点:违背了单一职责原则,代码的可测试性较差。

使用案例:在前端框架中,可以用单例模式创建一个全局状态管理对象,对应于Vuex或Redux等状态管理库。

<template>
  <div>
    <p>Count: {{ $store.state.count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
export default {
  methods: {
    increment() {
      this.$store.dispatch('increment');
    },
    decrement() {
      this.$store.dispatch('decrement');
    },
  },
};
</script>

这里vue组件中通过this.$store来访问和修改全局的状态。

2.观察者模式


定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布订阅模式

优点:可实现对象之间的松耦合,当一个对象状态发生变化时,其他对象能够自动更新。

缺点:如果观察者过多或者耗时操作过多,会导致性能问题。

使用案例:在前端框架中,可以用观察者模式实现事件机制,例如Vue中的响应式原理就是基于观察者模式。

<template>
  <div>
    <p>Counter: {{ counter }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { reactive, watch } from 'vue';

export default {
  setup() {
    const data = reactive({
      counter: 0,
    });

    watch(() => data.counter, (newValue) => {
      console.log('Counter changed:', newValue);
    });

    function increment() {
      data.counter++;
    }

    return {
      counter: data.counter,
      increment,
    };
  },
};
</script>

以上代码中,使用Vue3提供的reactivewatch函数来实现观察者模式。通过reactivedata对象变成响应式的,然后使用watch监听data.counter的变化,并在变化时输出日志。

在模板中,可以直接引用counter变量和increment方法,当点击按钮时,increment方法会更新data.counter的值,触发watch中的回调函数。

通过观察者模式,Vue能够实时检测到数据的变化,并自动更新相关视图,实现了响应式原理。这使得开发者可以方便地编写数据驱动的代码,无需手动去更新视图。

3.工厂模式


定义:工厂模式定义创建对象的接口,但是让子类决定实例化哪个类。工厂方法将类的实例化延迟到子类。

优点:封装了对象的创建过程,提供了统一的接口来创建对象,易于扩展和维护。

缺点:增加了系统的复杂性,需要对对象的具体类型有一定的了解。

使用案例:在前端框架中,可以用工厂模式创建不同类型的组件,例如Vue中的组件工厂函数。

<template>
  <div>
    <button @click="createComponent">Create Component</button>
    <div ref="componentContainer"></div>
  </div>
</template>

<script>
import { createApp, h } from 'vue';

export default {
  methods: {
    createComponent() {
      const app = createApp({
        template: '<div>Dynamic Component</div>',
      });

      const componentInstance = app.mount(this.$refs.componentContainer);
    },
  },
};
</script>

在上述代码中,我们定义了一个按钮,当点击按钮时,调用createComponent方法。该方法创建了一个app实例,其中包含了一个简单的组件配置,该组件在template中定义为一个div,显示 "Dynamic Component" 字符串。

接下来,通过调用app.mount方法,将组件实例挂载到$refs.componentContainer元素中,使其显示在页面上。

通过组件工厂函数,我们可以动态地创建组件实例,这在需要根据不同情况生成具有特定配置的组件时非常有用。例如,可以根据用户的权限动态生成不同的菜单组件,或者在特定条件下动态生成不同样式的弹窗组件等。

4.代理模式


定义:它提供了一个代理对象来控制对另一个对象的访问。代理对象充当了被代理对象的接口,并且可以在不改变原有逻辑的情况下,加入额外的功能或控制对被代理对象的访问。

优点

  • 职责分离:代理对象负责控制和管理对目标对象的访问,实现了被代理对象和代理对象的分离。
  • 增强原有功能:代理对象可以在原有逻辑的基础上添加额外的功能,比如性能优化、安全验证、缓存等。
  • 控制对目标对象的访问:代理对象可以控制对目标对象的访问权限,只允许符合条件的访问请求。
  • 提高性能:通过代理对象预处理耗时操作或延迟加载,可以提高系统性能。

缺点

  • 增加了系统的复杂性:引入代理对象会增加额外的类和逻辑,对于简单的场景可能会显得过于繁琐。
  • 增加了系统的开销:代理模式需要维护额外的对象和方法调用,可能会引入一定的性能开销。

使用案例: 在前端框架中,可以使用代理模式创建一个全局状态管理对象,对应于Vuex或Redux等状态管理库。通过代理对象,可以控制对全局状态的访问,实现状态的集中管理和跨组件共享。代理对象可以添加额外的功能,比如在状态改变时进行通知或记录日志,从而增强全局状态的管理能力。

// 创建全局状态管理对象的代理
const stateProxy = {
  state: {
    count: 0,
  },

  // 读取全局状态的方法
  getState() {
    return this.state;
  },

  // 更新全局状态的方法
  updateState(newState) {
    this.state = { ...this.state, ...newState };
    this.notifyObservers();
  },

  // 观察者模式,用于跨组件通信
  observers: [],

  addObserver(observer) {
    this.observers.push(observer);
  },

  removeObserver(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  },

  notifyObservers() {
    this.observers.forEach((observer) => observer.update());
  },
};

// 组件A
const componentA = {
  update() {
    const state = stateProxy.getState();
    console.log('Component A received state update:', state);
  }
};

// 组件B
const componentB = {
  update() {
    const state = stateProxy.getState();
    console.log('Component B received state update:', state);
  }
};

// 通过代理对象访问全局状态
stateProxy.addObserver(componentA);
stateProxy.addObserver(componentB);

// 更新全局状态
stateProxy.updateState({ count: 1 });

在上述案例中,首先创建了一个名为stateProxy的代理对象,它包含了全局状态的属性state、读取全局状态的方法getState()、更新全局状态的方法updateState(),以及用于跨组件通信的观察者模式相关方法。

然后,定义了两个组件componentAcomponentB,它们都实现了update()方法,用于接收和处理全局状态的更新通知。

最后,通过代理对象stateProxyaddObserver()方法,将组件componentAcomponentB添加为观察者。当调用stateProxyupdateState()方法更新全局状态时,观察者会收到相应的更新通知,并执行对应的update()方法来处理更新后的全局状态。

这样,通过代理对象stateProxy,可以实现全局状态的集中管理和跨组件共享,同时利用观察者模式来实现组件间的通信。这种代理模式的应用,类似于Vuex状态管理库在前端框架中的作用。

最后

引用一下课程ppt中的忠告:

设计模式不是"银弹" 总结出抽象的模式相对比较简单,但是想要将抽象的模式套 用到场景中却非常困难 现代编程语言的多编程范式带来的更多可能性 真正优秀的开源项目学习设计模式并不断实践

参考


前端框架中常用的设计模式 --知乎 JavaScript中的工厂模式简述

JavaScript设计模式与实践--工厂模式

前端最重要的设计模式: 观察者模式

前端设计模式之代理模式