【设计模式】03-理解常见设计模式-行为型模式(专栏完结)

news2025/2/21 6:10:02

前言

前面我们介绍完创建型模式和创建型模式,这篇介绍最后的行为型模式,也是【设计模式】专栏的最后一篇。


一、概述

行为型模式主要用于处理对象之间的交互和职责分配,以实现更灵活的行为和更好的协作。

二、常见的行为型模式

1、观察者模式(Observer Pattern)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。

解释:可以把它想象成一个明星和粉丝的关系,明星(被观察对象)的一举一动(状态改变)都会被粉丝(观察者)关注到,当明星有新动态时,粉丝会收到消息。

代码示范以及解释 

// 被观察对象类
class Subject {
    constructor() {
        this.observers = [];
    }

    // 添加观察者
    addObserver(observer) {
        this.observers.push(observer);
    }

    // 移除观察者
    removeObserver(observer) {
        const index = this.observers.indexOf(observer);
        if (index!== -1) {
            this.observers.splice(index, 1);
        }
    }

    // 通知所有观察者
    notify() {
        this.observers.forEach(observer => observer.update());
    }
}

// 观察者类
class Observer {
    constructor(name) {
        this.name = name;
    }

    update() {
        console.log(`${this.name} has been notified.`);
    }
}

// 使用示例
const subject = new Subject();
const observer1 = new Observer('Fan1');
const observer2 = new Observer('Fan2');

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify(); 
// 输出:
// Fan1 has been notified.
// Fan2 has been notified.

 被观察的对象Subject

  • 构造函数 constructor
    • 初始化一个空数组 this.observers,用于存储所有注册的观察者。
  • addObserver 方法
    • 接收一个 observer 对象作为参数,将其添加到 this.observers 数组中。这就相当于有一个新的观察者开始关注这个被观察对象了。
  • removeObserver 方法
    • 接收一个 observer 对象作为参数,首先使用 indexOf 方法查找该观察者在 this.observers 数组中的索引。
    • 如果索引不为 -1(说明该观察者存在于数组中),则使用 splice 方法将其从数组中移除,意味着这个观察者不再关注被观察对象了。
  • notify 方法
    • 遍历 this.observers 数组,对每个观察者调用其 update 方法。这样就可以通知所有注册的观察者,让它们执行相应的更新操作。

 观察者Observer

  • 构造函数 constructor
    • 接收一个 name 参数,用于标识这个观察者,将其存储在 this.name 中。
  • update 方法
    • 当被观察对象调用 notify 方法通知观察者时,这个 update 方法会被执行。它会打印出一条消息,表明该观察者已经收到了通知。

代码执行结果解释

  • 首先创建一个 Subject 类的实例 subject,表示被观察对象。
  • 接着创建两个 Observer 类的实例 observer1 和 observer2,分别命名为 'Fan1' 和 'Fan2'
  • 调用 subject.addObserver 方法将 observer1 和 observer2 添加到 subject 的观察者列表中。
  • 最后调用 subject.notify() 方法,subject 会遍历其观察者列表,依次调用每个观察者的 update 方法,因此控制台会输出 'Fan1 has been notified.' 和 'Fan2 has been notified.'

2、策略模式(Strategy Pattern):

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式让算法的变化独立于使用算法的客户。

解释:策略模式定义了一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式让算法的变化独立于使用算法的客户。这就好比你要去上班,有多种出行方式(策略)可供选择,如坐公交、打车、骑自行车等,你可以根据当天的实际情况(如时间、天气等)来动态地选择合适的出行方式,而上班这个行为本身不受具体出行方式的影响。

 代码示范以及解释 

// 定义不同的策略类
// 公交出行策略
class BusStrategy {
    travel() {
        console.log('Taking the bus to work.');
    }
}
//这是一个公交出行策略类,包含一个 travel 方法。
//当调用 travel 方法时,会在控制台打印出 Taking the bus to work.,表示选择乘坐公交去上班。

// 打车出行策略
class TaxiStrategy {
    travel() {
        console.log('Taking a taxi to work.');
    }
}
//这是一个打车出行策略类,同样有一个 travel 方法。
//调用 travel 方法时,会在控制台打印出 Taking a taxi to work.,表示选择打车去上班。

// 自行车出行策略
class BicycleStrategy {
    travel() {
        console.log('Riding a bicycle to work.');
    }
}
//这是一个自行车出行策略类,travel 方法会在控制台打印出 Riding a bicycle to work.,表示选择骑自行车去上班。

// 环境类,负责使用策略
class Commute {
    constructor(strategy) {
        this.strategy = strategy;
    }

    setStrategy(strategy) {
        this.strategy = strategy;
    }

    goToWork() {
        this.strategy.travel();
    }
}
//构造函数 constructor:
//接收一个 strategy 参数,将传入的策略对象赋值给 this.strategy,表示初始化时使用该策略。
//setStrategy 方法:
//接收一个新的 strategy 参数,将 this.strategy 更新为新的策略对象,从而实现策略的动态切换。
//goToWork 方法:
//调用当前 this.strategy 对象的 travel 方法,执行具体的出行策略。

// 使用示例
const busStrategy = new BusStrategy();
const commute = new Commute(busStrategy);
commute.goToWork(); // 输出: Taking the bus to work.

const taxiStrategy = new TaxiStrategy();
commute.setStrategy(taxiStrategy);
commute.goToWork(); // 输出: Taking a taxi to work.

执行结果解释

  • 首先创建一个 BusStrategy 类的实例 busStrategy,表示公交出行策略。
  • 接着创建一个 Commute 类的实例 commute,并将 busStrategy 作为参数传入,意味着初始化时使用公交出行策略。
  • 调用 commute.goToWork() 方法,会执行公交出行策略,控制台输出 Taking the bus to work.
  • 然后创建一个 TaxiStrategy 类的实例 taxiStrategy,表示打车出行策略。
  • 调用 commute.setStrategy(taxiStrategy) 方法,将当前的出行策略切换为打车策略。
  • 再次调用 commute.goToWork() 方法,会执行打车出行策略,控制台输出 Taking a taxi to work.

3、发布-订阅模式(Publish - Subscribe Pattern)(重点!!)

发布 - 订阅模式和观察者模式类似,但它引入了一个中间者(消息代理),发布者(发布消息的对象)将消息发布到消息代理,订阅者(接收消息的对象)向消息代理订阅感兴趣的消息。这种模式实现了发布者和订阅者之间的解耦。

简单理解:就像一个报社和读者的关系,报社(发布者)负责发布报纸(消息),读者(订阅者)向报社订阅报纸,当有新报纸出版时,报社将报纸发送给订阅的读者。

 代码示范以及解释 

// 消息代理类
class EventEmitter {
    constructor() {
        this.events = {};
    }

    // 订阅事件
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    }

    // 发布事件
    emit(eventName, ...args) {
        if (this.events[eventName]) {
            this.events[eventName].forEach(callback => callback(...args));
        }
    }

    // 取消订阅事件
    off(eventName, callback) {
        if (this.events[eventName]) {
            const index = this.events[eventName].indexOf(callback);
            if (index!== -1) {
                this.events[eventName].splice(index, 1);
            }
        }
    }
}

// 使用示例
const eventEmitter = new EventEmitter();

// 订阅者的回调函数
const callback1 = (message) => {
    console.log(`Subscriber 1 received: ${message}`);
};

const callback2 = (message) => {
    console.log(`Subscriber 2 received: ${message}`);
};

// 订阅事件
eventEmitter.on('news', callback1);
eventEmitter.on('news', callback2);

// 发布事件
eventEmitter.emit('news', 'A new article is published!'); 
// 输出:
// Subscriber 1 received: A new article is published!
// Subscriber 2 received: A new article is published!

// 取消订阅
eventEmitter.off('news', callback1);
eventEmitter.emit('news', 'Another new article!'); 
// 输出:
// Subscriber 2 received: Another new article!

整体功能概述

EventEmitter 类充当消息代理,它允许用户订阅(on 方法)特定的事件,发布(emit 方法)事件,以及取消订阅(off 方法)事件。当事件被发布时,所有订阅该事件的回调函数都会被执行。

消息代理类 EventEmitter

  • 构造函数 constructor
    • 初始化一个空对象 this.events,用于存储事件及其对应的回调函数数组。每个事件名作为对象的键,对应的值是一个数组,数组中存储着订阅该事件的所有回调函数。
  • on 方法
    • 接收两个参数:eventName 表示事件的名称,callback 是订阅该事件时要执行的回调函数。
    • 首先检查 this.events 对象中是否已经存在该事件名。如果不存在,就为该事件名创建一个空数组。
    • 然后将传入的 callback 函数添加到该事件对应的数组中。这意味着又有一个订阅者订阅了该事件。
  • emit 方法
    • 接收事件名 eventName 和任意数量的参数 ...args
    • 检查 this.events 对象中是否存在该事件名。如果存在,就遍历该事件对应的回调函数数组,依次调用每个回调函数,并将 ...args 作为参数传递给它们。这样就实现了事件的发布,通知所有订阅者执行相应的操作。
  • off 方法
    • 接收事件名 eventName 和要取消订阅的回调函数 callback
    • 检查 this.events 对象中是否存在该事件名。如果存在,使用 indexOf 方法查找该回调函数在事件对应的数组中的索引。
    • 如果索引不为 -1(说明该回调函数存在于数组中),使用 splice 方法将其从数组中移除,从而实现取消订阅的功能。

执行结果解释

const eventEmitter = new EventEmitter();

// 订阅者的回调函数
const callback1 = (message) => {
    console.log(`Subscriber 1 received: ${message}`);
};

const callback2 = (message) => {
    console.log(`Subscriber 2 received: ${message}`);
};

// 订阅事件
eventEmitter.on('news', callback1);
eventEmitter.on('news', callback2);

// 发布事件
eventEmitter.emit('news', 'A new article is published!'); 
// 输出:
// Subscriber 1 received: A new article is published!
// Subscriber 2 received: A new article is published!

// 取消订阅
eventEmitter.off('news', callback1);
eventEmitter.emit('news', 'Another new article!'); 
// 输出:
// Subscriber 2 received: Another new article!
  • 创建一个 EventEmitter 类的实例 eventEmitter
  • 定义两个回调函数 callback1 和 callback2,分别表示两个订阅者在收到事件通知时要执行的操作。
  • 调用 eventEmitter.on 方法,将 callback1 和 callback2 订阅到 'news' 事件上。
  • 调用 eventEmitter.emit 方法发布 'news' 事件,并传递消息 'A new article is published!'。由于 callback1 和 callback2 都订阅了该事件,所以它们都会被执行,控制台会输出相应的消息。
  • 调用 eventEmitter.off 方法取消 callback1 对 'news' 事件的订阅。
  • 再次调用 eventEmitter.emit 方法发布 'news' 事件,并传递消息 'Another new article!'。此时只有 callback2 还订阅着该事件,所以只有 callback2 会被执行,控制台会输出相应的消息。

三、总结

 到此,【设计模式】专栏的篇章已经完结~

文章介绍的是部分常见的设计模式,实际上还有很多设计模式等着大家去学习,后续我有空会补充新的设计模式内容,敬请期待吧~

如果你喜欢这篇文章,留下你的三连+订阅~

关注我,及时获取最新文章消息~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2299493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

编程题-最大子数组和(中等-重点【贪心、动态规划、分治思想的应用】)

题目: 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 解法一(枚举法-时间复杂度超限): …

本地通过隧道连接服务器的mysql

前言 服务器上部署了 mysql,本地希望能访问该 mysql,但是又不希望 mysql 直接暴露在公网上 那么可以通过隧道连接 ssh 端口的方式进行连接 从外网看,服务器只开放了一个 ssh 端口,并没有开放 3306 监听端口 设置本地免密登录 …

2. grafana插件安装并接入zabbix

一、在线安装 如果不指定安装位置,则默认安装位置为/var/lib/grafana/plugins 插件安装完成之后需要重启grafana 命令在上一篇讲到过 //查看相关帮助 [rootlocalhost ~]# grafana-cli plugins --help //从列举中的插件过滤zabbix插件 [rootlocalhost ~]# grafana…

Linux第107步_Linux之PCF8563实验

使用PCF8563代替内核的RTC,可以降低功耗,提高时间的精度。同时有助于进一步熟悉I2C驱动的编写。 1、了解rtc_time64_to_tm()和rtc_tm_to_time64() 打开“drivers/rtc/lib.c” /* * rtc_time64_to_tm - Converts time64_t to rtc_time. * Convert seco…

功能说明并准备静态结构

功能说明并准备静态结构 <template><div class"card-container"><!-- 搜索区域 --><div class"search-container"><span class"search-label">车牌号码&#xff1a;</span><el-input clearable placeho…

[免费]SpringBoot公益众筹爱心捐赠系统【论文+源码+SQL脚本】

大家好&#xff0c;我是老师&#xff0c;看到一个不错的SpringBoot公益众筹爱心捐赠系统&#xff0c;分享下哈。 项目介绍 公益捐助平台的发展背景可以追溯到几十年前&#xff0c;当时人们已经开始通过各种渠道进行公益捐助。随着互联网的普及&#xff0c;本文旨在探讨公益事业…

ML.Net二元分类

ML.Net二元分类 文章目录 ML.Net二元分类前言项目的创建机器学习模型的创建添加模型选择方案训练环境的选择训练数据的添加训练数据的选择训练数据的格式要预测列的选择模型评估模型的使用总结前言 ‌ML.NET‌是由Microsoft为.NET开发者平台创建的免费、开源、跨平台的机器学习…

visutal studio 2022使用qcustomplot基础教程

编译 下载&#xff0c;2.1.1版支持到Qt6.4 。 拷贝qcustomplot.h和qcustomplot.cpp到项目源目录&#xff08;Qt project&#xff09;。 在msvc中将它俩加入项目中。 使用Qt6.8&#xff0c;需要修改两处代码&#xff1a; L6779 # if QT_VERSION > QT_VERSION_CHECK(5, 2, …

本地搭建自己的专属客服之OneApi关联Ollama部署的大模型并创建令牌《下》

这里写目录标题 OneApi1、渠道设置2、令牌创建 配置文件修改修改配置文件docker-compose.yml修改config.json到此结束 上文讲了如何本地docker部署fastGtp&#xff0c;相信大家也都已经部署成功了&#xff01;&#xff01;&#xff01; 今天就说说怎么让他们连接在一起 创建你的…

【C】初阶数据结构4 -- 双向循环链表

之前学习的单链表相比于顺序表来说&#xff0c;就是其头插和头删的时间复杂度很低&#xff0c;仅为O(1) 且无需扩容&#xff1b;但是对于尾插和尾删来说&#xff0c;由于其需要从首节点开始遍历找到尾节点&#xff0c;所以其复杂度为O(n)。那么有没有一种结构是能使得头插和头删…

小爱音箱控制手机和电视听歌的尝试

最近买了小爱音箱pro&#xff0c;老婆让我扔了&#xff0c;吃灰多年的旧音箱。当然舍不得&#xff0c;比小爱还贵&#xff0c;刚好还有一台红米手机&#xff0c;能插音箱&#xff0c;为了让音箱更加灵活&#xff0c;买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…

Kotlin Lambda

Kotlin Lambda 在探索Kotlin Lambda之前&#xff0c;我们先回顾下Java中的Lambda表达式&#xff0c;Java 的 Lambda 表达式是 Java 8 引入的一项强大的功能&#xff0c;它使得函数式编程风格的代码更加简洁和易于理解。Lambda 表达式允许你以一种更简洁的方式表示实现接口&…

Java 设计模式之备忘录模式

文章目录 Java 设计模式之备忘录模式概述UML代码实现 Java 设计模式之备忘录模式 概述 备忘录(Memento)&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态。方便对该对象恢复到原先保存的状态。 UML Originnato…

vue3搭建实战项目笔记二

vue3搭建实战项目笔记二 2.1.git管理项目2.2.隐藏tabBar栏2.2.1 方案一&#xff1a;在路由元信息中设置一个参数是否显示tabBar2.2.2 方案二&#xff1a;通过全局设置相对定位样式 2.3.项目里封装axios2.3.1 发送网络请求的两种做法2.3.2 封装axios并发送网络请求2.3.2.1 对axi…

【原创】解决vue-element-plus-admin无法实现下拉框动态控制表单功能,动态显隐输入框

前言 目前使用vue-element-plus-admin想要做一个系统定时任务功能&#xff0c;可以选择不同的定时任务类型&#xff0c;比如使用cron表达式、周期执行、指定时间执行等。每种类型对应不同的输入框&#xff0c;需要动态显隐输入框才行&#xff0c;但是这个vue-element-plus-adm…

大疆无人机需要的kml文件如何制作kml导出(大疆KML文件)

大疆无人机需要的轨迹kml文件&#xff0c;是一种专门的格式&#xff0c;这个kml里面只有轨迹点&#xff0c;其它的属性信息都不需要。 BigemapPro提供了专门的大疆格式输出&#xff0c; 软件这里下载 www.bigemap.com 安装后&#xff0c;kml导入如下图&#xff1a; 然后选择…

免费deepseek的API获取教程及将API接入word或WPS中

免费deepseek的API获取教程: 1 https://cloud.siliconflow.cn/中注册时填写邀请码&#xff1a;GAejkK6X即可获取2000 万 Tokens; 2 按照图中步骤进行操作 将API接入word或WPS中 1 打开一个word&#xff0c;文件-选项-自定义功能区-勾选开发工具-左侧的信任中心-信任中心设置…

(三)Axure制作转动的唱片

效果图 属性&#xff1a; 图标库&#xff1a;iconfont-阿里巴巴矢量图标库 方形图片转为圆角图片&#xff0c;裁剪&#xff0c;然后加圆角&#xff0c; 唱片和底图是两个图片&#xff0c;点击播放&#xff0c;唱片在旋转。 主要是播放按钮和停止按钮&#xff0c;两个动态面板…

ASP.NET Core SixLabors.ImageSharp 位图图像创建和下载

从 MVC 控制器内部创建位图图像并将其发送到浏览器&#xff1b;用 C# 编写并与 Linux 和 Windows 服务器兼容。 使用从 ASP.NET MVC 中的控制器下载任何文件类型File。 此示例创建一个位图 (jpeg) 并将其发送到浏览器。它需要 NuGet 包SixLabors.ImageSharp v1.0.4。 另请参…

机器学习所需要的数学知识【01】

总览 导数 行列式 偏导数 概理论 凸优化-梯度下降 kkt条件