• 前端设计模式和设计原则之设计模式


    作为前端开发,在code时,或多或少地都会践行设计模式,但是你清楚自己用到的是何种设计模式吗?

    为什么前端开发一定要懂设计模式呢?

    code时不遵从设计模式,又能怎样呢?

    上面的问题可以留作思考,这里首先介绍一下前端开发经常遇到的一些设计模式和设计原则

    前端常见的设计模式

    1 单例模式

    在整个应用程序中只允许创建一个实例的模式。
    在前端开发中,它通常用于管理全局状态或资源,例如:

    • React 应用中,使用 Redux 库来管理应用的状态;
    • Vue 应用中,使用 Vuex 库来管理应用的状态;
    • Angular 应用中,使用 Service 来管理应用的状态;

    angular 服务是可注入的类,用于提供共享的数据、功能或逻辑给整个应用程序的组件;由于服务是以单例形式存在的,每次注入服务都会返回同一个实例

    使用@Injectable({ providedIn: 'root' })装饰器将MyService注册为根级提供商,这意味着整个应用程序都可以访问该服务的单一实例。

    // my.service.ts
    
    import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root'
    })
    
    export class MyService {
      private count: number = 0;
    
      incrementCount() {
        this.count++;
      }
    
      getCount(): number {
        return this.count;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在组件中,可以通过依赖注入的方式来使用该服务;

    // my.component.ts
    
    import { Component } from '@angular/core';
    import { MyService } from './my.service';
    
    @Component({
      selector: 'my-component',
      template: `
        

    Count: {{ myService.getCount() }}

    `
    }) export class MyComponent { constructor(private myService: MyService) {} incrementCount() { this.myService.incrementCount(); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    拓展

    如果在Angular中的@Injectable装饰器的providedIn配置中不使用"root",而是指定其他模块或组件,那么该服务将在该模块或组件的范围内成为单例

    这意味着,服务将在指定的模块或组件及其子组件中共享同一个实例,而不是在整个应用程序中共享。这对于需要在特定范围内共享数据或功能的场景非常有用

    例如,假设有两个组件ComponentAComponentB,它们都引用了同一个服务SharedService,并且将该服务作为提供程序配置在它们各自的模块中。

    // shared.service.ts
    
    import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'other-module' // 指定其他模块,而不是 'root'
    })
    export class SharedService {
      public sharedData: string = 'Shared data';
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // component-a.component.ts
    
    import { Component } from '@angular/core';
    import { SharedService } from './shared.service';
    
    @Component({
      selector: 'component-a',
      template: `
        

    {{ sharedService.sharedData }}

    `
    }) export class ComponentA { constructor(public sharedService: SharedService) {} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    // component-b.component.ts
    
    import { Component } from '@angular/core';
    import { SharedService } from './shared.service';
    
    @Component({
      selector: 'component-b',
      template: `
        

    {{ sharedService.sharedData }}

    `
    }) export class ComponentB { constructor(public sharedService: SharedService) {} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这种情况下,SharedService将在ComponentAComponentB之间共享同一个实例,但其作用范围限定在这两个组件及其子组件中。

    这种用法可以让开发者更细粒度地控制服务的共享范围,使得不同的模块或组件可以拥有各自独立的服务实例。

    2 观察者模式

    2.1 Vue.js

    针对前端的观察者模式,一种常见的应用是使用Vue.js框架的响应式系统。Vue.js使用观察者模式来追踪数据的变化并更新视图。

    
    
    DOCTYPE html>
    <html>
    <head>
        <title>Vue.js Observer Exampletitle>
        <script src="https://unpkg.com/vue@2.6.14/dist/vue.js">script>
    head>
    <body>
        <div id="app">
            <h2>{{ message }}h2>
            <input v-model="message" type="text" placeholder="Type something...">
        div>
    
        <script>
            // Initialize Vue
            new Vue({
                el: '#app',
                data: {
                    message: 'Hello, Vue!'
                }
            });
        script>
    body>
    html>
    
    
    • 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

    上述代码中,使用了Vue.js来创建一个具有双向数据绑定的简单应用。message属性的值将被显示在页面上的 h2 标签中,并且可以通过输入框进行编辑。

    Vue.js的响应式系统会在message属性发生变化时自动更新页面中对应的内容。这是通过Vue.js在内部使用观察者模式来实现的。当message属性的值发生变化时,观察者会被通知,并执行相应的更新。

    这种观察者模式的应用使得开发者无需显式地修改DOM来更新视图,而是只需关注数据的变化,Vue.js会自动处理更新过程。这大大简化了前端开发中处理视图更新的任务。

    2.2 Angular

    Angular中,观察者模式主要通过使用RxJS(响应式扩展)库来实现。RxJS是一个强大的事件处理库,它提供了多种操作符和观察者模式的实现,以处理异步事件流。

    Angular中,通过使用Observables(可观察对象)和Subjects(主题),开发者可以实现观察者模式的效果,并在组件之间进行事件通信或数据共享

    下面是一个简单的示例,展示了如何在Angular中使用观察者模式实现组件之间的通信:

    // message.service.ts
    
    import { Injectable } from '@angular/core';
    import { Subject } from 'rxjs';
    
    @Injectable()
    export class MessageService {
      private messageSubject = new Subject<string>();
    
      message$ = this.messageSubject.asObservable();
    
      sendMessage(message: string) {
        this.messageSubject.next(message);
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上述代码创建了一个名为MessageService的服务,它使用Subject创建了一个消息主题。通过调用主题的next()方法,可以向订阅该主题的观察者发送消息。

    // component-a.component.ts
    
    import { Component } from '@angular/core';
    import { MessageService } from './message.service';
    
    @Component({
      selector: 'component-a',
      template: `
        
      `
    })
    export class ComponentA {
      constructor(private messageService: MessageService) {}
    
      sendMessage() {
        this.messageService.sendMessage('Hello from Component A!');
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码创建了一个名为ComponentA的组件,它通过依赖注入的方式引用了MessageService。在按钮的点击事件中,我们调用sendMessage()方法来发送一条消息。

    // component-b.component.ts
    
    import { Component } from '@angular/core';
    import { MessageService } from './message.service';
    
    @Component({
      selector: 'component-b',
      template: `
        

    {{ receivedMessage }}

    `
    }) export class ComponentB { receivedMessage: string = ''; constructor(private messageService: MessageService) {} ngOnInit() { this.messageService.message$.subscribe(message => { this.receivedMessage = message; }); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上述代码创建了一个名为ComponentB的组件,并在ngOnInit()生命周期钩子中订阅了MessageServicemessage$可观察对象。一旦有新的消息发送,观察者将接收到该消息并更新receivedMessage属性。

    使用上述代码,当ComponentA中的按钮被点击时,它将向ComponentB发送一条消息,并在ComponentB中更新显示的消息。这通过观察者模式实现组件之间的双向通信

    2.3 React

    React 中,观察者模式可以通过使用 Context API 和钩子函数来实现。下面是一个简单的示例:

    首先,创建一个观察者上下文(ObserverContext):

    import React, { createContext, useContext, useState } from 'react';
    
    const ObserverContext = createContext();
    
    export const ObserverProvider = ({ children }) => {
      const [observers, setObservers] = useState([]);
    
      const addObserver = (observer) => {
        setObservers((prevObservers) => [...prevObservers, observer]);
      };
    
      const removeObserver = (observer) => {
        setObservers((prevObservers) =>
          prevObservers.filter((o) => o !== observer)
        );
      };
    
      const notifyObservers = () => {
        observers.forEach((observer) => observer());
      };
    
      const contextValue = {
        addObserver,
        removeObserver,
        notifyObservers,
      };
    
      return (
        <ObserverContext.Provider value={contextValue}>
          {children}
        </ObserverContext.Provider>
      );
    };
    
    export const useObserver = (observer) => {
      const { addObserver, removeObserver } = useContext(ObserverContext);
    
      // 添加观察者
      useEffect(() => {
        addObserver(observer);
    
        // 组件卸载时移除观察者
        return () => removeObserver(observer);
      }, [observer, addObserver, removeObserver]);
    };
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    然后,在需要进行观察的组件中使用 useObserver 钩子来订阅和响应变化。

    import React, { useState } from 'react';
    import { useObserver } from './ObserverContext';
    
    const Counter = () => {
      const [count, setCount] = useState(0);
    
      const handleIncrement = () => {
        setCount((prevCount) => prevCount + 1);
      };
    
      // 添加观察者
      useObserver(() => {
        console.log('Count has changed:', count);
      });
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={handleIncrement}>Increment</button>
        </div>
      );
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在上述示例中,ObserverProvider 提供了观察者的上下文,并定义了添加观察者、移除观察者和通知观察者的方法。使用 useObserver 钩子来订阅观察者模式中的变化。当状态(这里是 count)发生变化时,观察者将被通知并执行相应的操作。

    使用这种方式,可以在 React 中实现简单的观察者模式,以便组件之间能够订阅和响应特定的事件或状态变化。

    3 代码工厂模式

    代码工厂模式是一种创建对象的设计模式,它通过使用工厂函数或类来封装对象的创建过程。在这种模式下,我们不直接调用对象的构造函数来创建对象,而是通过一个专门的工厂方法来统一管理对象的创建

    代码工厂模式的主要目的是隐藏具体对象创建的细节,并提供一种可扩展和灵活的方式来创建对象。它将对象的实例化逻辑封装在一个独立的组件中,使得创建对象的过程可以进行集中管理,而不是分散在应用程序的各个地方。

    3.1 Angular

    下面是一个简单的示例,展示了如何在 Angular 中使用代码工厂模式:

    import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root',
    })
    export class UserService {
      private users: string[] = [];
    
      addUser(user: string): void {
        this.users.push(user);
      }
    
      getUsers(): string[] {
        return this.users;
      }
    }
    
    @Injectable({
      providedIn: 'root',
    })
    export class UserFactory {
      constructor(private userService: UserService) {}
    
      createUser(name: string): void {
        this.userService.addUser(name);
      }
    }
    
    • 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

    在上述示例中,UserService 是一个服务类,它管理用户信息。UserFactory 是一个工厂类,负责创建用户并将其添加到 UserService 中。

    Angular 中,通过使用 @Injectable() 装饰器和 providedIn: 'root' 选项,我们可以将 UserServiceUserFactory 注册为可注入的服务,并确保它们在整个应用程序中的任何组件中都可用。

    然后,在其他组件中可以通过依赖注入的方式使用这些服务:

    import { Component } from '@angular/core';
    import { UserFactory } from './user.factory';
    
    @Component({...})
    export class AppComponent {
      constructor(private userFactory: UserFactory) {}
    
      createUser() {
        this.userFactory.createUser('John');
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在上述示例中,AppComponent 组件通过依赖注入 UserFactory 来使用工厂模式创建用户。

    通过在 Angular 中使用代码工厂模式,我们可以将对象的创建和初始化逻辑封装到可注入的服务中,并在需要时利用依赖注入方便地使用这些服务。这样可以提高代码的可维护性、扩展性和测试性。

    3.2 React

    下面是一个简单的示例,展示了如何在 React 中使用代码工厂模式:

    import React from 'react';
    
    // 工厂函数
    function createButton(type) {
      const Button = (props) => {
        let button;
    
        if (type === 'primary') {
          button = (
            <button className="primary-button" onClick={props.onClick}>
              {props.children}
            </button>
          );
        } else if (type === 'secondary') {
          button = (
            <button className="secondary-button" onClick={props.onClick}>
              {props.children}
            </button>
          );
        } else {
          throw new Error('Invalid button type');
        }
    
        return button;
      };
    
      return Button;
    }
    
    // 使用工厂函数创建按钮组件
    const PrimaryButton = createButton('primary');
    const SecondaryButton = createButton('secondary');
    
    // 使用按钮组件
    const App = () => (
      <div>
        <PrimaryButton onClick={() => console.log("Primary button clicked")}>
          Primary Button
        </PrimaryButton>
        <SecondaryButton onClick={() => console.log("Secondary button clicked")}>
          Secondary Button
        </SecondaryButton>
      </div>
    );
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    在上述例子中,createButton 是一个工厂函数,根据传入的 type 参数返回一个特定类型的按钮组件。根据不同的类型,创建不同样式、行为或功能的按钮。

    通过调用 createButton 工厂函数,可以轻松创建不同类型的按钮组件,并在应用程序中使用它们。这样,避免在多个地方重复编写相似的代码,而是通过工厂模式集中管理和创建组件。

    使用工厂模式,可以根据需要快速创建并定制化多个组件,并轻松地进行修改或扩展。这种模式提供了一种更灵活、可维护和可重用的方式来创建和管理 React 组件。

    4 策略模式

    策略模式用于定义一系列算法,并将其封装成独立的对象,使它们可以相互替换。在前端开发中,策略模式可以用来处理多种算法或逻辑的情况,例如在表单验证中根据不同规则进行验证。

    下面是一个简单的示例,用于根据不同的排序策略对数组进行排序:

    // 排序策略对象
    const sortingStrategies = {
      quickSort: (arr) => arr.sort((a, b) => a - b),
      mergeSort: (arr) => arr.sort((a, b) => b - a),
    };
    
    // 排序上下文对象
    class SortContext {
      constructor(strategy) {
        this.strategy = strategy;
      }
    
      setStrategy(strategy) {
        this.strategy = strategy;
      }
    
      sort(arr) {
        return this.strategy(arr);
      }
    }
    
    // 使用策略模式进行排序
    const arr = [5, 2, 8, 1, 4];
    const context = new SortContext(sortingStrategies.quickSort);
    console.log(context.sort(arr)); // 输出: [1, 2, 4, 5, 8]
    
    context.setStrategy(sortingStrategies.mergeSort);
    console.log(context.sort(arr)); // 输出: [8, 5, 4, 2, 1]
    
    
    • 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

    在上述示例中,sortingStrategies 是一个包含不同排序策略的对象,其中 quickSortmergeSort 是两种不同的排序算法。

    SortContext 是排序上下文对象,它接收一个排序策略作为参数,并提供 setStrategy 方法来动态更改当前的排序策略。sort 方法使用当前的排序策略对给定的数组进行排序。

    通过创建 SortContext 实例并设置不同的排序策略,我们可以根据需要选择特定的排序算法对数组进行排序。这样,我们可以在运行时根据需求灵活地切换算法,而不需要在每个地方都修改排序逻辑。

    策略模式使得应用程序更具可扩展性和灵活性,因为我们可以轻松添加新的策略或修改现有的策略,而无需修改已有的代码。同时,它还能提高代码的可读性和可维护性,使算法选择与实际执行逻辑分离开来。

  • 相关阅读:
    Linux系列之比较命令
    R语言dplyr包group_by函数和summarise_at函数计算dataframe计算不同分组的计数个数和均值、使用%>%符号将多个函数串起来
    BL808学习日志-2-LVGL for M0 and D0
    购物H5商城架构运维之路
    awk常用统计命令
    (附源码)springboot社区文明养宠平台 毕业设计 231609
    第一章习题
    单调栈题目:去除重复字母
    第05章 SpringBoot Web开发(二)
    8个维度,详细分析JDK11的新特性!
  • 原文地址:https://blog.csdn.net/weixin_45678402/article/details/132810545