观察者模式的理解和引用

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习】滴滴出行-交通场景目标检测

案例5:滴滴出行-交通场景目标检测 相关知识点:目标检测、开源框架的配置和使用(mmdetection, mmcv) 1 任务目标 1.1 任务和数据简介 本次案例将使用深度学习技术来完成城市交通场景下的目标检测任务,案例所使用的数…

CentOS7 安装ErLang语言环境

在线搜索适合当前linux系统的epel在线安装。 yum -y install epel-release下载erlang-solutions安装包。 wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm离线安装erlang-solutions安装包。 rpm -Uvh erlang-solutions-1.0-1.noarch.rpm在线…

项目性能优化—使用JMeter压测SpringBoot项目

项目性能优化—使用JMeter压测SpringBoot项目 我们的压力测试架构图如下: 配置JMeter 在JMeter的bin目录,双击jmeter.bat 新建一个测试计划,并右键添加线程组: 进行配置 一共会发生4万次请求。 ctrl s保存; 添加h…

Aigtek电压放大器的作用及优点是什么

电压放大器是电子技术领域中重要的设备,其作用是将输入信号的电压放大到所需的输出电压水平。电压放大器具有多种优点,下面安泰电子将详细介绍其作用及主要优点。 电压放大器的主要作用是增加信号的电压幅值。通过放大信号的电压,可以增强信号…

网络架构层_服务器上下行宽带

网络架构层_服务器上下行宽带 解释一 云服务器ECS网络带宽的概念、计费、安全及使用限制_云服务器 ECS(ECS)-阿里云帮助中心 网络带宽是指在单位时间(一般指的是1秒钟)内能传输的数据量,带宽数值越大表示传输能力越强,即在单位…

就业班 2401--3.13 走进网络

走进网络 长风破浪会有时,直挂云帆济沧海。 1.认识计算机 1.计算机网络是由计算机和通讯构成的,网络研究的是“通信”。 ------1946 世界上第一台计算机 2.终端:只有输入和输出功能,没有计算和处理功能。 3.数据:一串…

深入浅出Go的`encoding/xml`库:实战开发指南

深入浅出Go的encoding/xml库:实战开发指南 引言基本概念XML简介Go语言中的XML处理结构体标签(Struct Tags) 解析XML数据使用xml.Unmarshal解析XML结构体标签详解处理常见解析问题 生成XML数据使用xml.Marshal生成XML使用xml.MarshalIndent优化…

Linux服务器磁盘更改挂载目录

linux服务器磁盘弹性扩容时,会出现没有挂载到理想的目录下,这时候就需要通过命令从新挂载目录,以下示例是把默认挂载目录/home更改为/data 1,df -lh ####查看现有挂载信息 2.lsblk ###查看文件形式,确保原有数据盘文件结构。 3.…

团队如何限制合适的在制品(WIP)数量?

看板之父David Anderson曾说过“看板的本质是一个很朴素的思想:在制品必须被限制。”但对于团队来说,确定一个合适的在制品限制可能是件棘手的事。 在《看板快速启动指南》一文中,我们已经初步了解如何打造一个看板,今天我们来一…

java学习之路-方法讲解

目录 1.方法概念及使用 1.1什么是方法 1.2方法定义 1.3 方法调用的执行过程 1.4 实参和形参的关系(重要) 1.5 没有返回值的方法 2.方法重载 3.方法递归 3.1递归概念 3.2递归执行过程分析 3.3递归练习 代码示例1 代码示例2 1.方法概念及使用 1.1什么是方法 方法就是…

jetson nano——编译一些包的网址导航,pyside2,qt(持续更新)

目录 1.PySide2下载地址2.tesserocr下载地址3.Qt下载地址4.OpenSSL官网5.latex编译器下载地址5.1MikTex5.2TeX Live 1.PySide2下载地址 https://download.qt.io/official_releases/QtForPython/pyside2/ 如下图: 2.tesserocr下载地址 https://github.com/simonflue…

python网络编程:通过socket实现TCP客户端和服务端

目录 写在开头 socket服务端(基础) socket客户端(基础) 服务端实现(可连接多个客户端) 客户端实现 数据收发效果 写在开头 近期可能会用python实现一些网络安全工具,涉及到许多关于网络…

PythonWeb——Django框架

框架介绍 1.什么是框架? 框架就是程序的骨架,主体结构,也是个半成品。 2.框架的优缺点 可重用、成熟,稳健、易扩展、易维护 3.Python中常见的框架 大包大揽 Django被官方称之为完美主义者的Web框架。力求精简web.py和Tornado新生代微框架Flask和B…

hadoop分布式环境ssh设置免密登陆之后目标主机更换无法连接解决

在进行hadoop分布式环境搭建时(三台机,master,slave1,slave2),后期slave2系统出现问题,更换新机后,master与slave2文件传输失败: 以为是秘钥过期的问题,更换…

【Linux】一文解决如何在终端查看 python解释器 的位置

【Linux】一文解决如何在终端查看 python解释器 的位置 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程👈 希望得到您的订阅…

Github主页设置贪吃蛇详细教程

先看最终实现结果: 有条贪吃蛇放在主页还是蛮酷的哈哈哈。接下来我来讲一讲怎么在Github主页添加一条贪吃蛇。 首先要修改自己的Github的主页,我们得有一个特殊的仓库——这个仓库必须与你的Github用户名保持一致,并且需要公开&#xff0c…

静默快速安装oracle 19c

静默快速安装oracle 19c 1.配置yum源 1.配置网络yum源 1.删除redhat7.0系统自带的yum软件包; rpm -qa|grep yum >oldyum.pkg 备份原信息rpm -qa|grep yum|xargs rpm -e --nodeps 不检查依赖,直接删除rpm包 1232.自行下载所需要的软件包。包名会…

求解3、4、6自由度仿射变换矩阵

说明:一开始将目光放在了opencv上,发现只有4、6自由度的仿射变换求解,后来发现skimage十分强大。 注:美中不足的是,skimage的实现没有RANSAC。 function:skimage.transform.estimate_transform() ttypeeu…

【SpringMVC】SpringMVC的整体执行流程

概述:MVC是一种设计模式,SpringMVC是按照MVC模式实现的优秀框架,可以帮助我们更简洁的完成Web开发,并且天然与Spring集成。后端项目分为Service层(处理业务)、Dao层(数据库操作)、En…

Java复习03 多线程

Java复习03 多线程 初学的时候 我的问题是 多线程是什么意思?进程和线程的区别? 线程创建的方式是什么?线程的状态是什么意思?分为哪几类?线程同步又是什么意思?有哪几种情况 ?静态代理是什么意…