前端框架中的设计模式 | 豆包MarsCode AI刷题

108 阅读19分钟

前端8种常用设计模式

1. 发布 - 订阅模式

1. 定义与概念

  • 发布 - 订阅模式(Publish - Subscribe Pattern)是一种软件设计模式。在前端开发中,它用于处理组件之间的通信,特别是当一个对象的状态变化需要通知其他多个对象时。这种模式包含了发布者(Publisher)和订阅者(Subscriber)两种角色。
  • 发布者负责发布消息,它并不关心谁(哪些订阅者)会接收到消息。订阅者则是对某些消息感兴趣,它们会向发布者订阅自己感兴趣的消息类型,当发布者发布相应消息时,订阅者就会收到通知并执行相应的操作。

2. 简单示例

  • 假设我们在一个网页中有多个部分需要根据用户登录状态进行更新。我们可以创建一个发布 - 订阅系统来处理这种情况。
  • 首先,我们定义一个事件中心(Event Hub)来管理订阅和发布操作。以下是一个简单的 JavaScript 示例:
const eventHub = {
    // 存储订阅者信息的对象,键是事件类型,值是订阅者函数的数组
    subscribers: {},
    // 订阅方法
    subscribe: function (eventType, callback) {
        if (!this.subscribers[eventType]) {
            this.subscribers[eventType] = [];
        }
        this.subscribers[eventType].push(callback);
    },
    // 发布方法
    publish: function (eventType, data) {
        const subscribers = this.subscribers[eventType] || [];
        subscribers.forEach((callback) => {
            callback(data);
        });
    }
};
  • 这里,eventHub就是发布 - 订阅模式中的中心枢纽。subscribe方法用于订阅事件,它接收一个事件类型(如'userLoggedIn')和一个回调函数。回调函数是当事件发布时要执行的操作。publish方法用于发布事件,它会遍历所有订阅了指定事件类型的回调函数,并将数据传递给它们。
  • 现在,假设我们有两个组件,一个是导航栏(Navbar),一个是用户信息面板(UserInfoPanel),它们都需要在用户登录时更新。
  • 对于导航栏组件:
function Navbar() {
    eventHub.subscribe('userLoggedIn', function (userData) {
        console.log(`导航栏更新:欢迎 ${userData.username}`);
        // 实际应用中可以在这里更新DOM元素,显示用户登录状态等操作
    });
}
  • 对于用户信息面板组件:
function UserInfoPanel() {
    eventHub.subscribe('userLoggedIn', function (userData) {
        console.log(`用户信息面板更新:显示 ${userData.username} 的详细信息`);
        // 同样可以在这里更新DOM元素,显示用户头像、姓名等信息
    });
}
  • 当用户登录成功后,我们可以发布用户登录的消息:
function userLoginSuccess(userData) {
    eventHub.publish('userLoggedIn', userData);
}

3. 在前端框架中的应用

  • Vue.js:Vue.js 有自己的事件系统,它在组件之间的通信中也体现了发布 - 订阅的思想。例如,在父子组件通信中,父组件可以通过$emit方法发布一个自定义事件,子组件可以通过v - on(缩写为@)来订阅这个事件。 - 假设我们有一个父组件ParentComponent和一个子组件ChildComponent。 - 父组件模板:
<template>
    <div>
        <button @click="sendMessage">向子组件发送消息</button>
        <ChildComponent @parentMessage="handleChildResponse"/>
    </div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
    components: {
        ChildComponent
    },
    methods: {
        sendMessage() {
            this.$emit('parentMessage', '这是来自父组件的消息');
        },
        handleChildResponse(message) {
            console.log('父组件收到子组件的响应:', message);
        }
    }
};
</script>
  • 子组件模板:
<template>
    <div>
        <p>等待父组件消息</p>
    </div>
</template>
<script>
export default {
    methods: {
        handleParentMessage(message) {
            console.log('子组件收到父组件消息:', message);
            this.$emit('childResponse', '子组件已收到消息');
        }
    },
    mounted() {
        this.$on('parentMessage', this.handleParentMessage);
    }
};
</script>
  • 这里,父组件通过$emit发布parentMessage事件,子组件通过$on订阅这个事件。当事件触发时,子组件执行handleParentMessage方法,并可以通过$emit发布自己的响应事件,父组件再通过@v - on)来订阅这个响应事件。
  • React.js:虽然 React 本身没有内置像 Vue 这样的事件系统,但可以使用第三方库如pubsub - js来实现发布 - 订阅模式。
  • 首先安装pubsub - jsnpm install pubsub - js
  • 以下是一个简单的示例:
  • 定义一个消息发布者组件:
import PubSub from 'pubsub - js';
import React, { Component } from 'react';
class PublisherComponent extends Component {
    handlePublishMessage = () => {
        const message = '这是一条发布的消息';
        PubSub.publish('messagePublished', message);
    };
    render() {
        return (
            <button onClick={this.handlePublishMessage}>发布消息</button>
        );
    }
}
  • 定义一个消息订阅者组件:
import PubSub from 'pubsub - js';
import React, { Component } from 'react';
class SubscriberComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            receivedMessage: null
        };
    }
    componentDidMount() {
        const token = PubSub.subscribe('messagePublished', (msg, data) => {
            this.setState({
                receivedMessage: data
            });
        });
        // 保存token,以便在组件卸载时取消订阅
        this.token = token;
    }
    componentWillUnmount() {
        PubSub.unsubscribe(this.token);
    }
    render() {
        return (
            <div>
                {this.state.receivedMessage? (
                    <p>收到消息:{this.state.receivedMessage}</p>
                ) : (
                    <p>等待消息</p>
                )}
            </div>
        );
    }
}

4. 优点

  • 解耦组件:发布 - 订阅模式使得组件之间的依赖关系更加松散。发布者和订阅者不需要直接知道对方的存在,它们只通过事件中心进行通信。这样在修改一个组件时,不会影响到其他组件,只要事件的接口(事件类型和传递的数据结构)不变。
  • 可扩展性高:可以方便地添加新的订阅者。例如,在网页应用中,如果需要新增一个组件来根据用户登录状态更新,只需要在这个新组件中订阅userLoggedIn事件即可,而不需要对发布者和其他订阅者进行大量的修改。
  • 灵活性好:同一个事件可以有多个不同的订阅者,每个订阅者可以根据自己的需求对事件进行不同的处理。比如在用户登录事件中,导航栏可能只更新欢迎信息,而用户信息面板可以更新用户的详细信息和权限相关内容。

5. 缺点

  • 增加系统复杂性:引入了事件中心,使得代码的逻辑流程可能会变得不那么直观。对于初学者来说,理解事件的发布、订阅和传递过程可能会有一定的难度。
  • 可能导致内存泄漏:如果订阅者没有正确地取消订阅(特别是在单页应用中,组件的生命周期比较复杂),当组件被销毁后,仍然可能会收到事件通知,这可能会导致内存泄漏或者其他意想不到的错误。因此,在组件销毁时,一定要记得取消订阅相关的事件。

2. 单例模式(Singleton Pattern)

1. 定义与概念

  • 单例模式(Singleton Pattern)是一种创建型设计模式。它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在前端开发中,单例模式用于管理共享资源,例如全局配置对象、日志记录器、数据库连接池等,避免多个实例造成资源浪费或数据不一致的情况。

1. 实现方式

  • 简单的 JavaScript 实现
  • 以下是一个基本的单例模式的 JavaScript 示例。我们创建一个名为Singleton的对象。
const Singleton = (function () {
    let instance;
    function createInstance() {
        return {
            // 这里可以定义单例对象的属性和方法
            property: '这是单例对象的属性',
            method: function () {
                console.log('这是单例对象的方法');
            }
        };
    }
    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
  • 在这个示例中,Singleton是一个立即执行函数表达式(IIFE)。它内部有一个instance变量,用于存储单例对象的实例。createInstance函数用于创建这个实例,它返回一个包含属性和方法的对象。getInstance函数是对外提供的访问点,当第一次调用getInstance时,instance为空,会创建一个新的实例,之后再调用getInstance就会返回已经创建好的实例。

- 使用 ES6 类实现单例模式(JavaScript)

  • 另一种方式是使用 ES6 的类来实现单例模式。
class SingletonClass {
    constructor() {
        if (!SingletonClass.instance) {
            SingletonClass.instance = this;
            this.property = '这是单例对象的属性';
            this.method = function () {
                console.log('这是单例对象的方法');
            };
        }
        return SingletonClass.instance;
    }
}
const singletonInstance1 = new SingletonClass();
const singletonInstance2 = new SingletonClass();
console.log(singletonInstance1 === singletonInstance2); // true
  • 在这个类实现中,SingletonClass的构造函数检查是否已经存在instance属性。如果不存在,就将当前对象(this)赋值给instance,并定义属性和方法。如果instance已经存在,就直接返回这个已经存在的实例,这样就保证了只有一个实例被创建。

3. 在前端框架中的应用

  • Vue.js 中的单例应用场景
    • 在 Vue.js 应用中,全局事件总线(Event Bus)可以看作是一种单例模式的应用。它用于在不同组件之间进行通信,而不需要通过父子组件的 props 和事件传递的方式。
    • 例如,我们可以创建一个全局事件总线来处理用户登录状态的通知。首先,在main.js(Vue 应用的入口文件)中创建事件总线:
import Vue from 'vue';
const eventBus = new Vue();
export default eventBus;
  • 然后,在组件中可以使用这个事件总线来发布和订阅事件。比如一个登录组件可以发布用户登录成功的事件:
import eventBus from './eventBus';
export default {
    methods: {
        userLoginSuccess() {
            eventBus.$emit('userLoggedIn', {
                username: '示例用户'
            });
        }
    }
};
  • 其他组件可以订阅这个事件,例如一个导航栏组件:
import eventBus from './eventBus';
export default {
    created() {
        eventBus.$on('userLoggedIn', (userData) => {
            console.log(`导航栏更新:欢迎 ${userData.username}`);
            // 实际应用中可以更新DOM元素显示用户登录状态
        });
    }
};
  • 这里的eventBus就是一个单例对象,整个应用中只有一个这样的Vue实例用于事件的发布和订阅,避免了创建多个事件发布 - 订阅系统造成的混乱。

- React.js 中的单例应用场景

-   在 React.js 中,单例模式可以用于管理全局状态,比如使用 Redux 等状态管理库。Redux 中的`store`通常是单例的。
-   例如,我们有一个简单的 Redux 存储设置。首先,定义一个`reducer`来处理状态变化:
const initialState = {
    count: 0
};
function counterReducer(state = initialState, action) {
    switch (action.type) {
        case 'INCREMENT':
            return {
               ...state,
                count: state.count + 1
            };
        default:
            return state;
    }
}
  • 然后创建一个单例的store
import { createStore } from 'redux';
const store = createStore(counterReducer);
export default store;
  • 在 React 组件中,可以通过store来获取和更新状态。多个组件可以共享这个store,因为它是单例的,这样就保证了整个应用的状态是统一管理的。例如,一个组件用于增加计数:
import React from 'react';
import store from './store';
function IncrementButton() {
    const handleIncrement = () => {
        store.dispatch({
            type: 'INCREMENT'
        });
    };
    return (
        <button onClick={handleIncrement}>增加计数</button>
    );
}
  • 另一个组件用于显示计数:
import React from 'react';
import store from './store';
function CounterDisplay() {
    const count = store.getState().count;
    return (
        <p>计数:{count}</p>
    );
}

4. 优点

  • 资源共享:单例模式可以有效地共享资源。例如,对于一个应用中的配置对象,只需要一个实例就可以在各个部分进行访问和修改,避免了创建多个相同配置对象造成的内存浪费。
  • 全局访问点:提供了一个统一的全局访问点,使得代码的各个部分可以方便地获取到单例对象。这对于管理全局状态或者共享功能非常有用,如全局的日志记录器,各个模块都可以通过相同的方式访问来记录日志。
  • 控制实例数量:能够严格控制类的实例数量,确保在整个应用中只有一个实例,避免了因为多个实例可能导致的数据不一致或冲突问题。

5. 缺点

  • 违反单一职责原则:单例类可能会承担过多的职责,因为它要负责自身的实例化、管理以及提供全局访问点。例如,一个单例的数据库连接池对象,除了管理连接池的连接,还要保证自身的单例性质,这使得类的职责不够单一。
  • 不利于单元测试:由于单例模式的全局性质,在单元测试时可能会比较困难。如果一个单例对象在多个测试用例中被使用,可能会导致测试之间相互干扰,因为单例对象的状态可能会在不同测试用例之间产生混淆。
  • 可能导致代码耦合:如果单例对象在多个模块中被广泛使用,可能会导致这些模块与单例对象之间的耦合度过高。当需要修改单例对象的内部实现或者接口时,可能会影响到大量使用该单例对象的模块。

3. 策略模式

1. 定义与概念

  • 策略模式是一种行为设计模式。它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。在前端开发中,策略模式允许在运行时根据不同的条件选择不同的行为或算法,而不需要对使用这些行为的代码进行大量修改。
  • 简单来说,策略模式把算法的选择和算法的实现分离开来,使得算法可以独立地变化。例如,在一个电商网站中,计算商品总价的方式可能会根据不同的促销活动(如满减、折扣、赠品等)而有所不同,这些不同的计算方式就可以看作是不同的策略。

2. 结构组成

  • 策略(Strategy)接口或抽象类:定义了策略的方法签名,所有具体的策略类都要实现这个接口或继承这个抽象类。例如,在计算商品总价的例子中,这个接口可以是一个名为calculateTotalPrice的方法。
  • 具体策略(Concrete Strategy)类:实现了策略接口或抽象类,提供了具体的算法实现。比如针对满减策略、折扣策略等分别有对应的具体策略类,它们都实现了calculateTotalPrice方法,但内部的计算逻辑不同。
  • 上下文(Context)类:持有一个策略对象的引用,用于调用策略对象的方法。在前端应用场景中,上下文类可能是购物车对象,它通过调用策略对象的calculateTotalPrice方法来计算总价。

3. 简单示例(JavaScript)

  • 首先定义一个策略接口,这里以一个简单的函数形式表示:
// 策略接口(以函数形式)
function calculateTotalPrice(price, quantity) {
    throw new Error('该方法需要在具体策略类中实现');
}
  • 然后创建具体的策略类,比如一个折扣策略类:
// 折扣策略
function DiscountStrategy(rate) {
    this.rate = rate;
}
DiscountStrategy.prototype.calculateTotalPrice = function (price, quantity) {
    return price * quantity * this.rate;
};
  • 再创建一个满减策略类:
// 满减策略
function FullReductionStrategy(full, reduction) {
    this.full = full;
    this.reduction = reduction;
}
FullReductionStrategy.prototype.calculateTotalPrice = function (price, quantity) {
    const total = price * quantity;
    if (total >= this.full) {
        return total - this.reduction;
    }
    return total;
}
  • 接着定义上下文类,这里假设是购物车类:
function ShoppingCart() {
    this.strategy;
}
ShoppingCart.prototype.setStrategy = function (strategy) {
    this.strategy = strategy;
};
ShoppingCart.prototype.calculateTotal = function (price, quantity) {
    return this.strategy.calculateTotalPrice(price, quantity);
};
  • 使用示例:
const cart = new ShoppingCart();
const discountStrategy = new DiscountStrategy(0.8);
cart.setStrategy(discountStrategy);
const totalPrice1 = cart.calculateTotal(100, 2);
console.log(totalPrice1); // 160

const fullReductionStrategy = new FullReductionStrategy(200, 50);
cart.setStrategy(fullReductionStrategy);
const totalPrice2 = cart.calculateTotal(100, 3);
console.log(totalPrice2); // 250

4. 在前端框架中的应用

  • Vue.js 中的应用 - 在 Vue 组件中,策略模式可以用于处理不同的表单验证策略。例如,一个表单可能有多种字段,如文本字段、邮箱字段、密码字段等,每个字段的验证策略不同。 - 首先定义验证策略接口:
const validateStrategy = {
    validate: function (value) {
        throw new Error('该方法需要在具体验证策略类中实现');
    }
};
  • 然后是具体的验证策略,比如邮箱验证策略:
const emailValidateStrategy = {
    validate: function (value) {
        const emailRegex = /^[a - zA - Z0 - 9_.+-]+@[a - zA - Z0 - 9 -]+.[a - zA - Z0 - 9-.]+$/;
        return emailRegex.test(value);
    }
};
  • 文本字段验证策略:
const textValidateStrategy = {
    validate: function (value) {
        return value.length > 0;
    }
};
  • 在 Vue 组件中使用这些策略进行表单验证:
Vue.component('my - form', {
    template: `
        <div>
            <input v - model="email" placeholder="请输入邮箱">
            <input v - model="text" placeholder="请输入文本">
            <button @click="validateForm">验证表单</button>
        </div>
    `,
    data() {
        return {
            email: '',
            text: ''
        };
    },
    methods: {
        validateForm() {
            const emailValid = emailValidateStrategy.validate(this.email);
            const textValid = textValidateStrategy.validate(this.text);
            if (emailValid && textValid) {
                console.log('表单验证通过');
            } else {
                console.log('表单验证未通过');
            }
        }
    }
});

- React.js 中的应用

  • 在 React 中,策略模式可以用于处理不同的组件渲染策略。例如,一个列表组件可能根据不同的数据类型(如数组、对象等)有不同的渲染策略。
  • 首先定义渲染策略接口:
const renderStrategy = {
    render: function (data) {
        throw new Error('该方法需要在具体渲染策略类中实现');
    }
};
  • 然后是具体的渲染策略,比如数组渲染策略:
const arrayRenderStrategy = {
    render: function (data) {
        return data.map((item, index) => <li key={index}>{item}</li>);
    }
};
  • 对象渲染策略:
const objectRenderStrategy = {
    render: function (data) {
        return Object.keys(data).map((key) => <li key={key}>{key}: {data[key]}</li>);
    }
};
  • 在 React 组件中使用这些策略进行渲染:
class MyList extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            data: [],
            strategy: arrayRenderStrategy
        };
    }
    componentDidMount() {
        // 假设这里获取数据并更新state
        const data = [1, 2, 3];
        this.setState({
            data
        });
    }
    changeStrategy() {
        // 假设切换到对象渲染策略
        this.setState({
            strategy: objectRenderStrategy
        });
    }
    render() {
        const { data, strategy } = this.state;
        return (
            <div>
                <ul>
                    {strategy.render(data)}
                </ul>
                <button onClick={this.changeStrategy.bind(this)}>切换渲染策略</button>
            </div>
        );
    }
}
  1. 优点
    • 可维护性高:每个策略都是一个独立的类或函数,当需要修改某个策略的实现时,只需要在对应的策略类或函数中进行修改,不会影响到其他策略和使用这些策略的上下文代码。
    • 可扩展性强:可以方便地添加新的策略,只要新策略实现了策略接口或抽象类。例如,在电商网站的总价计算中,如果新增一种促销策略(如买一送一),只需要创建一个新的具体策略类并实现calculateTotalPrice方法即可。
    • 代码复用性好:不同的上下文可以复用相同的策略。例如,多个购物车对象可以复用折扣策略、满减策略等,提高了代码的复用程度。
  2. 缺点
    • 策略类增多:随着策略的增加,策略类或函数的数量会增多,可能会导致代码结构变得复杂,特别是当策略之间有一些相似性时,可能会出现代码重复的情况。
    • 客户端需要了解策略:客户端(使用策略的代码)需要知道有哪些策略可供选择,并且需要选择合适的策略。在一些复杂的系统中,这可能会增加客户端代码的复杂性。

4. 适配器模式

1. 定义与概念

  • 适配器模式是一种结构型设计模式。它的主要作用是将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。就像是一个转接头,把一种形状的插头(接口)转换为另一种形状的插座(另一个接口)能够接受的形式。
  • 例如,在前端开发中,可能会有一个新的第三方库提供了一些功能,但其接口和我们现有代码所期望的接口不同,这时就可以使用适配器模式来使它们兼容。

2. 结构组成

  • 目标(Target)接口:这是客户端所期望的接口。客户端代码通过这个接口来调用方法,它定义了客户端想要使用的方法签名。
  • 被适配者(Adaptee)类:这是需要被适配的类,它有自己的接口和方法实现,但这个接口与目标接口不兼容。
  • 适配器(Adapter)类:它实现了目标接口,并且在内部包含一个被适配者的实例。适配器类的方法会调用被适配者的相应方法来完成功能,从而将被适配者的接口转换为目标接口。

3. 简单示例(JavaScript)

  • 假设我们有一个旧的函数,它的接口不符合我们新的需求。
  • 被适配者(旧函数):
// 被适配者函数,接口不符合期望
function oldFunction(data) {
    console.log('旧函数执行,数据为: ', data);
}
  • 目标接口(期望的函数接口):
// 目标接口函数签名
function targetFunction(arg1, arg2) {
    throw new Error('该方法需要在适配器中实现');
}
  • 适配器类(函数):
// 适配器函数
function adapterFunction(arg1, arg2) {
    const data = {
        arg1: arg1,
        arg2: arg2
    };
    oldFunction(data);
}
  • 使用示例:
adapterFunction('参数1', '参数2');

4. 在前端框架中的应用

  • Vue.js 中的应用 - 在 Vue 组件中,当整合不同的数据源或者外部库时可能会用到适配器模式。例如,有一个外部的图表库,它的数据格式要求是一个特定的数组结构,但我们从后端获取的数据格式与之不同。 - 假设外部图表库期望的数据格式是[{name: '类别1', value: 10}, {name: '类别2', value: 20}],而后端返回的数据格式是{category: ['类别1', '类别2'], data: [10, 20]}。 - 首先定义目标接口,即图表库所期望的数据获取函数:
function getDataForChart() {
    throw new Error('该方法需要在适配器中实现');
}
  • 被适配者是后端获取数据的函数:
function getBackendData() {
    return {
        category: ['类别1', '类别2'],
        data: [10, 20]
    };
}
  • 适配器函数:
function adapterForChartData() {
    const backendData = getBackendData();
    const adaptedData = backendData.category.map((name, index) => ({
        name: name,
        value: backendData.data[index]
    }));
    return adaptedData;
}
  • 在 Vue 组件中使用:
Vue.component('chart - component', {
    template: '<div>图表组件</div>',
    mounted() {
        const data = adapterForChartData();
        // 这里假设将data传递给图表库进行渲染
        console.log('传递给图表库的数据: ', data);
    }
});

- React.js 中的应用

  • 在 React 中,假设我们有一个旧的 API 返回的数据格式与 React 组件期望的数据格式不同。例如,旧 API 返回的用户信息是{user_id: 1, user_name: '张三', user_email: 'zhangsan@example.com'},而 React 组件期望的用户信息格式是{id: 1, name: '张三', email: 'zhangsan@example.com'}
  • 定义目标接口(React 组件期望的数据获取函数):
function getUserData() {
    throw new Error('该方法需要在适配器中实现');
}
  • 被适配者是旧 API 获取数据的函数:
function getOldUserData() {
    return {
        user_id: 1,
        user_name: '张三',
        user_email: 'zhangsan@example.com'
    };
}
  • 适配器函数:
function adapterForUserData() {
    const oldData = getOldUserData();
    const adaptedData = {
        id: oldData.user_id,
        name: oldData.user_name,
        email: oldData.user_email
    };
    return adaptedData;
}
  • 在 React 组件中使用:
class UserInfoComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            userData: null
        };
    }
    componentDidMount() {
        const data = adapterForUserData();
        this.setState({
            userData: data
        });
    }
    render() {
        const { userData } = this.state;
        if (!userData) {
            return <div>加载中...</div>;
        }
        return (
            <div>
                <p>用户ID: {userData.id}</p>
                <p>用户姓名: {userData.name}</p>
                <p>用户邮箱: {userData.email}</p>
            </div>
        );
    }
}

5. 优点

  • 提高代码兼容性:可以使原本不兼容的接口能够协同工作,有助于整合旧系统和新系统、不同的库或者不同格式的数据,避免了因为接口差异而重写大量代码。
  • 符合开闭原则:在不修改原有代码(被适配者)的基础上,通过创建适配器来实现新的接口需求,使得系统更容易维护和扩展。当需要接入新的不兼容的接口时,只需要创建新的适配器即可。

6. 缺点

  • 增加代码复杂度:每添加一个适配器,就会增加新的类或函数,可能会使代码结构变得复杂,特别是当有多个适配器时,需要仔细管理和维护这些适配器。
  • 性能开销可能增加:适配器在转换接口的过程中可能会涉及到数据的重新组织、格式转换等操作,这些额外的操作可能会带来一定的性能开销,不过在大多数前端应用场景中,这种开销通常是可以接受的。

5. 责任链模式

1. 定义与概念

  • 责任链模式是一种行为设计模式。它将请求的发送者和接收者解耦,让多个对象都有机会处理这个请求。这些对象连成一条链,当有请求到来时,会沿着这条链传递,直到有一个对象处理这个请求为止。就像是一个工作流程审批,一个申请可能会经过多个审批人,每个审批人都有自己的职责范围来决定是否处理这个申请。

1. 结构组成

  • 抽象处理者(Handler) :它定义了一个处理请求的接口,并且通常包含一个指向下一个处理者的引用。这个抽象类或者接口的主要方法是处理请求的方法,在方法中会判断是否自己处理请求,如果不能处理就将请求传递给下一个处理者。
  • 具体处理者(Concrete Handler) :实现了抽象处理者的接口,它们是责任链中的实际节点。每个具体处理者都有自己的处理逻辑,用于判断是否处理请求。如果自己能够处理就处理,不能处理就将请求传递给下一个处理者。

1. 简单示例(JavaScript)

  • 首先定义抽象处理者:
class AbstractHandler {
    constructor() {
        this.nextHandler;
    }
    setNext(handler) {
        this.nextHandler = handler;
    }
    handle(request) {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        return null;
    }
}
  • 然后创建具体处理者,例如处理小于 10 的数字的请求:
class SmallNumberHandler extends AbstractHandler {
    handle(request) {
        if (request < 10) {
            console.log(`SmallNumberHandler处理了请求: ${request}`);
            return;
        }
        return super.handle(request);
    }
}
  • 再创建处理大于等于 10 且小于 20 的数字的请求的处理者:
class MediumNumberHandler extends AbstractHandler {
    handle(request) {
        if (request >= 10 && request < 20) {
            console.log(`MediumNumberHandler处理了请求: ${request}`);
            return;
        }
        return super.handle(request);
    }
}
  • 最后创建处理大于等于 20 的数字的请求的处理者:
class LargeNumberHandler extends AbstractHandler {
    handle(request) {
        if (request >= 20) {
            console.log(`LargeNumberHandler处理了请求: ${request}`);
            return;
        }
        return super.handle(request);
    }
}
  • 使用示例:
const smallHandler = new SmallNumberHandler();
const mediumHandler = new MediumNumberHandler();
const largeHandler = new LargeNumberHandler();
smallHandler.setNext(mediumHandler);
mediumHandler.setNext(largeHandler);
smallHandler.handle(5);    // SmallNumberHandler处理了请求: 5
smallHandler.handle(15);   // MediumNumberHandler处理了请求: 15
smallHandler.handle(25);   // LargeNumberHandler处理了请求: 25

4. 在前端框架中的应用

  • Vue.js 中的应用
    • 在表单验证中可以使用责任链模式。例如,一个表单有多个验证规则,如非空验证、格式验证(如邮箱格式、手机号码格式)等。
    • 首先定义抽象验证处理者:
class AbstractValidator {
    constructor() {
        this.nextValidator;
    }
    setNext(validator) {
        this.nextValidator = validator;
    }
    validate(value) {
        if (this.nextValidator) {
            return this.nextValidator.validate(value);
        }
        return null;
    }
}
  • 然后是具体验证处理者,比如非空验证:
class NonEmptyValidator extends AbstractValidator {
    validate(value) {
        if (value) {
            return super.validate(value);
        }
        console.log('值不能为空');
        return false;
    }
}
  • 邮箱格式验证:
class EmailValidator extends AbstractValidator {
    validate(value) {
        const emailRegex = /^[a - zA - Z0 - 9_.+-]+@[a - zA - Z0 - 9 -]+.[a - zA - Z0 - 9-.]+$/;
        if (emailRegex.test(value)) {
            return super.validate(value);
        }
        console.log('请输入正确的邮箱格式');
        return false;
    }
}
  • 在 Vue 组件中使用:
Vue.component('my - form', {
    template: `
        <div>
            <input v - model="email">
            <button @click="validateForm">验证表单</button>
        </div>
    `,
    data() {
        return {
            email: ''
        };
    },
    methods: {
        validateForm() {
            const nonEmptyValidator = new NonEmptyValidator();
            const emailValidator = new EmailValidator();
            nonEmptyValidator.setNext(emailValidator);
            const result = nonEmptyValidator.validate(this.email);
            console.log('表单验证结果:', result);
        }
    }
});

- React.js 中的应用

  • 在事件处理中可以应用责任链模式。例如,在一个复杂的用户操作场景中,一个点击事件可能需要经过多个处理步骤,如权限验证、数据验证、操作执行等。
  • 首先定义抽象事件处理者:
class AbstractEventHandler {
    constructor() {
        this.nextHandler;
    }
    setNext(handler) {
        this.nextHandler = handler;
    }
    handleEvent(event) {
        if (this.nextHandler) {
            return this.nextHandler.handleEvent(event);
        }
        return null;
    }
}
  • 具体的事件处理者,比如权限验证:
class PermissionValidatorHandler extends AbstractEventHandler {
    handleEvent(event) {
        // 假设这里进行权限验证
        const hasPermission = true; // 实际应用中需要真正的验证逻辑
        if (hasPermission) {
            return super.handleEvent(event);
        }
        console.log('你没有权限执行此操作');
        return false;
    }
}
  • 数据验证处理者:
class DataValidatorHandler extends AbstractEventHandler {
    handleEvent(event) {
        // 假设这里进行数据验证
        const validData = true; // 实际应用中需要真正的验证逻辑
        if (validData) {
            return super.handleEvent(event);
        }
        console.log('数据无效');
        return false;
    }
}
  • 在 React 组件中使用:
class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            // 其他状态
        };
    }
    handleClick = (event) => {
        const permissionHandler = new PermissionValidatorHandler();
        const dataHandler = new DataValidatorHandler();
        permissionHandler.setNext(dataHandler);
        const result = permissionHandler.handleEvent(event);
        console.log('事件处理结果:', result);
    };
    render() {
        return (
            <div>
                <button onClick={this.handleClick}>执行操作</button>
            </div>
        );
    }
}

5. 优点

  • 解耦请求发送者和接收者:请求的发送者不需要知道具体是哪个对象来处理请求,只需要将请求发送到责任链的开头即可。这样降低了系统的耦合度,使得系统更加灵活。
  • 动态组合处理者:可以根据实际情况动态地组合处理者,增加或减少处理者的数量,改变处理者的顺序等,而不需要修改请求发送者的代码。
  • 增强系统的可扩展性:当需要添加新的处理规则或者功能时,只需要创建新的具体处理者并将其加入到责任链中即可,方便系统的扩展。

6. 缺点

  • 性能问题:如果责任链过长,请求可能需要经过多个对象的传递和判断才能得到处理,这可能会导致性能下降,特别是在处理性能敏感的场景中需要注意。
  • 调试困难:由于请求在多个对象之间传递,当出现问题时,定位具体是哪个处理者出现问题可能会比较复杂,需要仔细跟踪请求在责任链中的传递过程。

6. 策略模式

1. 定义与概念

  • 享元模式是一种结构型设计模式。它主要用于减少创建对象的数量,以减少内存占用和提高性能。其核心思想是共享对象,通过共享尽可能多的对象状态,使得可以用较少的对象来表示更多的情况。
  • 例如,在一个文本编辑器中,如果要显示大量的字母 “a”,这些 “a” 的外观(字体、颜色等)相同,那么可以使用享元模式,让这些相同外观的 “a” 字符共享同一个对象表示,而不是为每个 “a” 都创建一个新的对象。

2. 结构组成

  • 享元(Flyweight)接口或抽象类:定义了对象的共享方法和属性。所有具体的享元类都要实现这个接口或者继承这个抽象类,这些方法用于操作对象的内部状态。
  • 具体享元(Concrete Flyweight)类:实现了享元接口或抽象类,具体实现了共享方法和属性。这些类包含了可以被共享的对象状态。
  • 享元工厂(Flyweight Factory)类:负责创建和管理享元对象。它维护一个享元对象的池,当需要一个享元对象时,先在池中查找是否已经存在,如果存在就直接返回,不存在则创建一个新的享元对象并放入池中。

1. 简单示例(JavaScript)

  • 首先定义享元接口:
// 享元接口
class CharacterFlyweight {
    constructor() {
        this.color = 'black';
    }
    draw() {
        throw new Error('该方法需要在具体享元类中实现');
    }
}
  • 然后创建具体享元类,例如字母 “A” 的享元类:
// 具体享元类(字母A)
class CharacterA extends CharacterFlyweight {
    draw() {
        console.log(`绘制黑色的字母A`);
    }
}
  • 接着创建享元工厂类:
// 享元工厂类
class CharacterFlyweightFactory {
    constructor() {
        this.flyweights = {};
    }
    getFlyweight(key) {
        if (!this.flyweights[key]) {
            if (key === 'A') {
                this.flyweights[key] = new CharacterA();
            }
        }
        return this.flyweights[key];
    }
}
  • 使用示例:
const factory = new CharacterFlyweightFactory();
const characterA1 = factory.getFlyweight('A');
const characterA2 = factory.getFlyweight('A');
characterA1.draw();
characterA2.draw();
console.log(characterA1 === characterA2); // true

4. 在前端框架中的应用

  • Vue.js 中的应用
    • 在 Vue 组件中,当需要大量相同样式的组件时可以使用享元模式。例如,一个列表中有许多相同样式的图标组件。
    • 首先定义享元接口(以组件选项对象形式):
const IconFlyweight = {
    props: ['iconName'],
    template: '<i :class="iconClass"></i>',
    computed: {
        iconClass() {
            return `icon-${this.iconName}`;
        }
    },
    methods: {
        draw() {
            throw new Error('该方法需要在具体享元类中实现');
        }
    }
};
  • 然后创建具体享元类(例如一个特定图标组件):
const StarIcon = {
   ...IconFlyweight,
    draw() {
        console.log('绘制星星图标');
    }
};
  • 接着创建享元工厂类:
const IconFlyweightFactory = {
    icons: {},
    getIcon(iconName) {
        if (!this.icons[iconName]) {
            if (iconName === 'star') {
                this.icons[iconName] = StarIcon;
            }
        }
        return this.icons[iconName];
    }
};
  • 在 Vue 组件中使用:
Vue.component('icon - list', {
    template: `
        <div>
            <component :is="iconFactory.getIcon('star')" v - for="i in 5"></component>
        </div>
    `,
    data() {
        return {
            iconFactory: IconFlyweightFactory
        };
    }
});

- React.js 中的应用

  • 在 React 中,当渲染大量相同样式的元素时可以考虑享元模式。例如,一个页面中有很多相同颜色和大小的圆形。
  • 首先定义享元接口(以函数组件形式):
const CircleFlyweight = (props) => {
    const { color, size } = props;
    const style = {
        backgroundColor: color,
        width: size,
        height: size,
        borderRadius: '50%'
    };
    return <div style={style}></div>;
};
  • 然后创建享元工厂类:
const CircleFlyweightFactory = {
    circles: {},
    getCircle(color, size) {
        const key = `${color}-${size}`;
        if (!this.circles[key]) {
            this.circles[key] = CircleFlyweight({ color, size });
        }
        return this.circles[key];
    }
};
  • 在 React 组件中使用:
const CircleList = () => {
    const factory = CircleFlyweightFactory;
    return (
        <div>
            {[1, 2, 3].map((i) => (
                <div key={i}>
                    {factory.getCircle('red', '20px')}
                    {factory.getCircle('red', '20px')}
                </div>
            ))}
        </div>
    );
};

5. 优点

  • 节省内存:通过共享对象,可以大大减少对象的数量,尤其是在处理大量相似对象的场景下,能够显著节省内存空间。
  • 提高性能:由于对象的创建和销毁是比较消耗资源的操作,享元模式减少了对象的创建次数,从而在一定程度上提高了性能。
  • 易于维护和管理:享元对象的共享状态集中在享元类和享元工厂中管理,当需要修改共享状态时,只需要在这些地方进行修改,而不需要在大量的对象实例中逐个修改。

6. 缺点

  • 增加复杂性:享元模式引入了享元工厂和对象池等概念,使得代码结构相对复杂。需要额外的代码来管理享元对象的创建、查找和共享。
  • 共享状态的限制:对象的共享状态需要仔细设计和规划。如果共享状态设计不合理,可能会导致一些对象无法正确共享,或者在共享过程中出现状态冲突等问题。
  • 不适合所有场景:对于那些对象状态变化频繁、对象之间差异较大的场景,享元模式可能并不适用,因为很难找到合适的共享状态来实现对象的共享。

7. 状态模式

1. 定义与概念

  • 状态模式是一种行为设计模式。它允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类,实际上是通过切换不同的状态对象来实现不同的行为。这种模式将状态相关的行为封装到不同的状态类中,使得状态的转换和行为的执行更加清晰和易于维护。
  • 例如,一个简单的音乐播放器,它有播放、暂停、停止等状态。在不同的状态下,用户操作(如点击按钮)会引发不同的行为,比如在播放状态下点击暂停按钮会暂停播放,在暂停状态下点击播放按钮会恢复播放。

1. 结构组成

  • 上下文(Context)类:它维护一个对当前状态对象的引用,并且提供一些方法用于客户端调用。这些方法会委托给当前状态对象来执行实际的操作。上下文类通常还包含用于切换状态的方法。
  • 状态(State)接口或抽象类:定义了状态相关行为的接口,所有具体的状态类都要实现这个接口或者继承这个抽象类。这些接口方法代表了在该状态下对象可以执行的操作。
  • 具体状态(Concrete State)类:实现了状态接口或抽象类,每个具体状态类封装了在特定状态下的行为逻辑。当上下文对象的状态改变时,具体状态类中的方法会被调用以执行相应的行为。

1. 简单示例(JavaScript)

  • 首先定义状态接口:
class PlayerState {
    constructor(player) {
        this.player = player;
    }
    play() {
        throw new Error('该方法需要在具体状态类中实现');
    }
    pause() {
        throw new Error('该方法需要在具体状态类中实现');
    }
    stop() {
        throwError('该方法需要在具体状态类中实现');
    }
}
  • 然后创建具体状态类,例如播放状态类:
class PlayingState extends PlayerState {
    play() {
        console.log('已经在播放状态,无需操作');
    }
    pause() {
        console.log('暂停播放');
        this.player.setState(this.player.pausedState);
    }
    stop() {
        console.log('停止播放');
        this.player.setState(this.player.stoppedState);
    }
}
  • 暂停状态类:
class PausedState extends PlayerState {
    play() {
        console.log('恢复播放');
        this.player.setState(this.player.playingState);
    }
    pause() {
        console.log('已经在暂停状态,无需操作');
    }
    stop() {
        console.log('停止播放');
        this.player.setState(this.player.stoppedState);
    }
}
  • 停止状态类:
class StoppedState extends PlayerState {
    play() {
        console.log('开始播放');
        this.player.setState(this.player.playingState);
    }
    pause() {
        console.log('已经停止,无法暂停');
    }
    stop() {
        console.log('已经停止,无需操作');
    }
}
  • 接着创建上下文类(音乐播放器):
class MusicPlayer {
    constructor() {
        this.playingState = new PlayingState(this);
        this.pausedState = new PausedState(this);
        this.stoppedState = new StoppedState(this);
        this.currentState = this.stoppedState;
    }
    setState(state) {
        this.currentState = state;
    }
    play() {
        this.currentState.play();
    }
    pause() {
        this.currentState.pause();
    }
    stop() {
        this.currentState.stop();
    }
}
  • 使用示例:
const player = new MusicPlayer();
player.play();
player.pause();
player.stop();

4. 在前端框架中的应用

  • Vue.js 中的应用
    • 在 Vue 组件中,状态模式可以用于实现具有多种状态的交互组件。例如,一个模态框组件有显示和隐藏两种状态。
    • 首先定义状态接口:
const ModalState {
    constructor(modal) {
        this.modal = modal;
    }
    show() {
        throw new Error('该方法需要在具体状态类中实现');
    }
    hide() {
        throw new Error('该方法需要在具体状态类中实现');
    }
}
  • 然后创建具体状态类,例如显示状态类:
const ModalShownState extends ModalState {
    show() {
        console.log('模态框已经显示,无需操作');
    }
    hide() {
        console.log('隐藏模态框');
        this.modal.setState(this.modal.hiddenState);
    }
}
  • 隐藏状态类:
const ModalHiddenState extends ModalState {
    show() {
        console.log('显示模态框');
        this.modal.setState(this.modal.shownState);
    }
    hide() {
        console.log('模态框已经隐藏,无需操作');
    }
}
  • 接着创建上下文类(模态框组件):
Vue.component('my - modal', {
    template: `
        <div v - if="isShown">模态框内容</div>
    `,
    data() {
        return {
            shownState: new ModalShownState(this),
            hiddenState: new ModalHiddenState(this),
            currentState: this.hiddenState,
            isShown: false
        };
    },
    methods: {
        setState(state) {
            this.currentState = state;
            this.isShown = (state instanceof ModalShownState);
        },
        show() {
            this.currentState.show();
        },
        hide() {
            this.currentState.hide();
        }
    }
});

- React.js 中的应用

  • 在 React 中,状态模式可以用于实现具有复杂状态转换的组件,如一个加载数据的组件,有加载中、加载成功、加载失败等状态。
  • 首先定义状态接口(以函数形式):
const DataLoadingState = (component) => {
    return {
        load() {
            console.log('正在加载,无需操作');
        },
        success() {
            console.log('加载成功');
            component.setState(component.successState);
        },
        failure() {
            console.log('加载失败');
            component.setState(component.failureState);
        }
    };
};
  • 然后创建具体状态类,例如加载成功状态类:
const DataSuccessState = (component) => {
    return {
        load() {
            console.log('已经加载成功,无需操作');
        },
        success() {
            console.log('已经加载成功,无需操作');
        },
        failure() {
            console.log('数据变为失败状态');
            component.setState(component.failureState);
        }
    };
};
  • 加载失败状态类:
const DataFailureState = (component) => {
    return {
        load() {
            console.log('重新加载');
            component.setState(component.loadingState);
        },
        success() {
            console.log('数据变为成功状态');
            component.setState(component.successState);
        },
        failure() {
            console.log('已经失败,无需操作');
        }
    };
};
  • 接着创建上下文类(加载数据组件):
class DataLoadingComponent extends React.Component {
    constructor(props) {
        super(props);
        this.loadingState = DataLoadingState(this);
        this.successState = DataSuccessState(this);
        this.failureState = DataFailureState(this);
        this.state = {
            currentState: this.loadingState
        };
    }
    load() {
        this.state.currentState.load();
    }
    success() {
        this.state.currentState.success();
    }
    failure() {
        this.state.currentState.failure();
    }
    render() {
        return (
            <div>
                {this.state.currentState.load()}
            </div>
        );
    }
}

5. 优点

  • 状态和行为的清晰分离:将不同状态下的行为封装到不同的状态类中,使得代码结构清晰,易于理解和维护。当需要修改某个状态下的行为时,只需要在对应的状态类中进行修改,不会影响其他状态。
  • 状态转换的逻辑集中管理:状态转换的逻辑在状态类和上下文类中进行集中管理,而不是分散在多个条件判断语句中。这样可以更容易地跟踪和理解状态的转换过程,减少错误。
  • 符合开闭原则:当需要添加新的状态或者修改现有状态的行为时,只需要创建新的状态类或者修改现有状态类,而不需要修改使用状态的上下文类的核心逻辑,易于扩展。

5. 缺点

  • 增加类的数量:每一个状态都需要一个对应的状态类,当状态数量较多时,会导致类的数量增加,使得代码结构变得复杂,增加了维护成本。
  • 状态之间的通信可能复杂:在一些情况下,不同状态类之间可能需要进行通信或者共享数据,这可能会使代码变得复杂,需要仔细设计状态类之间的接口和交互方式。

8. 命令模式

1. 定义与概念

  • 命令模式是一种行为设计模式。它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。简单来说,它把发出命令的责任和执行命令的责任分割开,将命令的请求者和执行者解耦。
  • 例如,在一个图形编辑软件中,用户可以执行各种操作,如绘制图形、移动图形、删除图形等。这些操作可以被看作是命令,通过命令模式,我们可以将每个操作封装成一个命令对象,方便管理和执行。

1. 结构组成

  • 命令(Command)接口或抽象类:定义了执行命令的方法,通常是一个execute方法。这个接口或抽象类是所有具体命令类的基础,用于规范命令的执行行为。
  • 具体命令(Concrete Command)类:实现了命令接口或抽象类,具体实现了execute方法。这些类包含了执行命令所需的具体操作,并且通常会持有接收者(Receiver)对象的引用,通过接收者来执行实际的操作。
  • 接收者(Receiver)类:执行具体的操作,是真正完成命令任务的对象。例如,在图形编辑软件中,接收者可能是图形绘制引擎,它负责根据命令绘制、移动或删除图形。
  • 调用者(Invoker)类:负责创建和发送命令,它持有一个命令对象,并在需要的时候调用命令对象的execute方法来执行命令。

1. 简单示例(JavaScript)

  • 首先定义命令接口:
class Command {
    constructor(receiver) {
        this.receiver = receiver;
    }
    execute() {
        throw new Error('该方法需要在具体命令类中实现');
    }
}
  • 然后创建具体命令类,例如绘制矩形命令类:
class DrawRectangleCommand extends Command {
    constructor(receiver, width, height) {
        super(receiver);
        this.width = width;
        this.height = height;
    }
    execute() {
        this.receiver.drawRectangle(this.width, this.height);
    }
}
  • 接收者类(图形绘制引擎):
class GraphicsEngine {
    drawRectangle(width, height) {
        console.log(`绘制一个宽度为${width},高度为${height}的矩形`);
    }
}
  • 调用者类:
class CommandInvoker {
    constructor(command) {
        this.command = command;
    }
    invoke() {
        this.command.execute();
    }
}
  • 使用示例:
const graphicsEngine = new GraphicsEngine();
const drawCommand = new DrawRectangleCommand(graphicsEngine, 100, 200);
const invoker = new CommandInvoker(drawCommand);
invoker.invoke();

4. 在前端框架中的应用

  • Vue.js 中的应用
    • 在 Vue 组件中,命令模式可以用于处理用户操作和业务逻辑的分离。例如,在一个表单组件中,用户提交表单的操作可以封装为一个命令。
    • 首先定义命令接口:
const FormCommand {
    constructor(form) {
        this.form = form;
    }
    execute() {
        throw new Error('该方法需要在具体命令类中实现');
    }
}
  • 然后创建具体命令类,例如表单提交命令类:
const SubmitFormCommand extends FormCommand {
    execute() {
        const formData = this.form.serialize();
        console.log('提交表单数据:', formData);
        // 这里可以添加实际的提交逻辑,如发送AJAX请求等
    }
}
  • 接收者类(表单组件):
Vue.component('my - form', {
    template: `
        <form ref="myForm">
            <input type="text" v - model="inputValue">
            <button @click="submitForm">提交表单</button>
        </form>
    `,
    data() {
        return {
            inputValue: ''
        };
    },
    methods: {
        serialize() {
            return {
                inputValue: this.inputValue
            };
        },
        submitForm() {
            const command = new SubmitFormCommand(this);
            const invoker = new CommandInvoker(command);
            invoker.invoke();
        }
    }
});
  • 调用者类(简单的命令调用器):
const CommandInvoker = {
    constructor(command) {
        this.command = command;
    }
    invoke() {
        this.command.execute();
    }
};

- React.js 中的应用

  • 在 React 中,命令模式可以用于处理用户交互事件。例如,在一个按钮组件中,点击按钮的操作可以封装为一个命令。
  • 首先定义命令接口(以函数形式):
const ButtonCommand = (receiver) => {
    return {
        execute() {
            throw new Error('该方法需要在具体命令类中实现');
        }
    };
};
  • 然后创建具体命令类,例如按钮点击显示消息命令类:
const ShowMessageCommand = (receiver, message) => {
    return {
        execute() {
            console.log(message);
        }
    };
};
  • 接收者类(简单的消息显示组件):
const MessageDisplay = () => {
    return (
        <div>
            <button onClick={() => {
                const command = ShowMessageCommand(this, '按钮被点击');
                const invoker = new CommandInvoker(command);
                invoker.invoke();
            }}>显示消息</button>
        </div>
    );
};
  • 调用者类(命令调用器):
const CommandInvoker = {
    constructor(command) {
        this.command = command;
    }
    invoke() {
        this.command.execute();
    }
};

5. 优点

  • 解耦请求者和执行者:命令模式将请求的发送者(调用者)和请求的执行者(接收者)分离,使得两者可以独立变化。这样可以更容易地修改、扩展或替换请求的执行逻辑,而不会影响到请求的发送部分。
  • 可扩展性强:可以方便地添加新的命令,只需要创建新的具体命令类并实现execute方法即可。这对于系统的功能扩展非常有利,例如在图形编辑软件中添加新的图形绘制命令。
  • 支持命令的排队、记录和撤销:由于命令被封装为对象,可以方便地对命令进行排队执行,记录命令的执行历史,以及实现撤销和重做操作。这在一些需要复杂操作管理的系统中非常有用。

6. 缺点

  • 增加代码复杂度:命令模式引入了较多的类和对象,如命令类、接收者类、调用者类等,这可能会使代码结构变得复杂,尤其是对于简单的应用场景,可能会增加不必要的复杂性。
  • 可能导致性能问题:在一些情况下,频繁地创建和销毁命令对象可能会导致性能下降。例如,在一个高性能要求的实时系统中,如果大量使用命令模式且没有合理的对象管理,可能会影响系统性能。