当前位置:   article > 正文

策略模式的理解和运用

策略模式的理解和运用

在之前的小游戏项目中,处理websocket长连接请求的时候,需要根据传递数据包的不同类型,进行不同的处理。为了实现这个场景,比较简单的方法就是使用if-else或者switch-case语句,根据条件进行判断。但是这导致了项目代码复杂混乱,不利用项目后期的维护和扩展。

策略模式和观察者模式都可以解决这个大量条件判断的问题,相比之下,观察者模式比较复杂,适用于更加复杂的场景。而策略模式则比较轻便,简单易上手。

关于观察者模式,感兴趣的朋友可以看这一篇博客:

观察者模式的理解和引用-CSDN博客

旧代码如下

  1. //在信息中枢处根据消息类型进行特定的处理
  2. switch requestPkg.Type {
  3. case pojo.CertificationType:
  4. //用户认证
  5. client.CertificationProcess(requestPkg)
  6. case pojo.CreateRoomType:
  7. //创建房间号,并将创建者加入房间
  8. client.CreateRoomProcess()
  9. case pojo.JoinRoomType:
  10. //1.加入房间的前提,先建立连接
  11. //2.完成用户认证
  12. //3.发送消息类型和房间号 Type uuid
  13. //只有完成上述步骤,才可以加入房间
  14. var data map[string]interface{}
  15. err = json.Unmarshal([]byte(requestPkg.Data), &data)
  16. if err != nil {
  17. fmt.Println("解析 JSON 失败:", err)
  18. return
  19. }
  20. uuidValue, ok := data["uuid"].(string)
  21. if !ok {
  22. fmt.Println("uuid 字段不存在或不是字符串类型")
  23. return
  24. }
  25. client.JoinRoomProcess(uuidValue)
  26. case pojo.RefreshScoreType:
  27. //什么是否进行分数更新,前端判断 type:RefreshScoreType, data:step、step、score
  28. //当用户的行为触发前端游戏机制的更新时,前端调用此接口,后端进行分数的转发 不需要做业务处理,直接转发即可
  29. fmt.Println("游戏交换中数据", client)
  30. client.RefreshScoreProcess(requestPkg)
  31. case pojo.DiscontinueQuitType:
  32. client.DiscontinueQuitProcess()
  33. case pojo.GameOverType:
  34. //游戏结束类型好像没有太大用,游戏结束的时候的提醒,通过分数更新就可以实现了
  35. fmt.Println("GameOverType")
  36. case pojo.HeartCheckType:
  37. //开启一个协程遍历hub中的Client,进行健康检测,生命时间是否会过期,如果过期进行逻辑删除和关闭连接
  38. if requestPkg.Data == "PING" {
  39. client.HeartCheckProcess()
  40. }
  41. }

策略模式介绍

策略模式是一种软件设计模式,它允许在运行时选择算法的行为。策略模式定义了一系列算法,并使它们能够相互替换,从而使算法可以独立于其使用者而变化。这意味着在实际使用中,可以在不修改其结构的情况下轻松地切换或替换算法。在我们的场景下,我们通过websocket连接获取到的数据包有很多的类型:CertificationType、CreateRoomType、JoinRoomType、RefreshScoreType、DiscontinueQuitType、GameOverType、HeartCheckType等类型,我们需要根据不同的类型对数据进行不同的处理。即在运行时,根据传入的Type值,选择不同的行为进行处理,使得我们可以在运行的过程中轻松切换行为。【这里可以将算法理解成对同样结构的输入,进行不同的处理。】

Context上下文对象类:该对象持有对策略接口的引用,可以根据需要动态地改变所使用的具体策略。这样,客户端调用上下文对象的方法时,就可以根据所设置的具体策略来执行相应的算法。

  1. //将Strategy策略作为Context上下文对象的一个字段
  2. //封装get、set、execStrategy(执行方法)
  3. type Context struct {
  4. Strategy handler.Strategy
  5. }
  6. func (c *Context) SetStrategy(strategy handler.Strategy) {
  7. c.Strategy = strategy
  8. }
  9. func (c *Context) GetStrategy() handler.Strategy {
  10. return c.Strategy
  11. }
  12. func (c *Context) ExecStrategy(client *pojo.Client, request req.WsReq) {
  13. c.Strategy.StrategyMethod(client, request)
  14. }

Strategy策略接口:定义接口,接口里面是StrategyMethod()方法,在Context对象的ExecStrategy()方法中被调用。

  1. type Strategy interface {
  2. StrategyMethod(client *pojo.Client, request req.WsReq)
  3. }

ConcreteStrategy具体策略类:该类实现Strategy接口,并且根据自身的需要定义StrategyMethod()方法具体的业务代码。

  1. type LifetimeCheck struct {
  2. }
  3. func (c *LifetimeCheck) StrategyMethod(client *pojo.Client, req req.WsReq) {
  4. if string(req.Data) == `"PING"` {
  5. client.User.Lifetime = client.User.Lifetime.Add(10 * time.Second)
  6. }
  7. wsResp := resp.WsResp{Type: req.Type, Data: "PONG"}
  8. if err := wsResp.WsResponseSuccess(client); err != nil {
  9. zap.L().Error("PING 返回出现错误")
  10. return
  11. }
  12. }

客户端代码:创建上下文对象,根据需求,去调用SetStrategy()方法,并执行所设置策略的行为。我们的选择可以通过Map进行,根据map的key获取对应的具体策略对象。

  1. func WsController(client *pojo.Client) {
  2. //map初始化之后就没有写入操作了,所以不存在线程安全的问题!!! 只会进行读取操作
  3. context := new(Context)
  4. //1.执行认证行为
  5. context.SetStrategy(new(specificHandler.CertificationStrategy))
  6. context.ExecStrategy(client, request)
  7. //2.执行心跳检测
  8. context.SetStrategy(new(specificHandler.LifetimeCheck))
  9. context.ExecStrategy(client, request)
  10. //1.执行聊天信息
  11. context.SetStrategy(new(specificHandler.ChatStrategy))
  12. context.ExecStrategy(client, request)
  13. }

通过这样改造之后,我们的代码就可以实现:

  1. 算法可以独立于客户端而变化,客户端可以更灵活地选择算法。
  2. 可以避免大量的条件语句,将算法的选择和使用分离开来,提高了代码的可维护性和扩展性。
  3. 可以方便地增加、删除或更改算法,不会影响到客户端的代码。

总的来说,策略模式可以帮助我们更好地组织和管理不同的算法,使系统更加灵活、可扩展和易于维护。

策略选择改进

读到这里,大家可以发现,其实还是没有办法解决我们代码里面出现的大量if - else语句,我们需要通过if - else来做判断,帮助我们选择对应的行为。其实,这里可以引入map帮助我们来做选择,如果命中map中的key,就返回对应的算法对象。
代码如下:

  1. func WsController(client *pojo.Client) {
  2. //map初始化之后就没有写入操作了,所以不存在线程安全的问题!!! 只会进行读取操作
  3. context := new(Context)
  4. handlerMap := map[string]handler.Strategy{}
  5. //对客户端发送过来的对应请求类型,进行注册操作
  6. handlerMap[CertificationType] = new(specificHandler.CertificationStrategy)
  7. handlerMap[ChatType] = new(specificHandler.ChatStrategy)
  8. handlerMap[CommentNotificationType] = new(specificHandler.CommentNotification)
  9. handlerMap[FansNotificationType] = new(specificHandler.FansNotification)
  10. handlerMap[CloseType] = new(specificHandler.CloseConnection)
  11. handlerMap[LifetimeCheckType] = new(specificHandler.LifetimeCheck)
  12. for {
  13. request := req.WsReq{}
  14. //循环读取对应的类型,并进行反序列化
  15. client.Conn.ReadJSON(&request)
  16. //通过定义的map,通过对应的数据类型获取算法对象。如果能够获取到更改上下文,并执行匹配到的策略,处理数据
  17. if strategy, ok := handlerMap[request.Type]; ok {
  18. context.SetStrategy(strategy)
  19. context.ExecStrategy(client, request)
  20. } else {
  21. zap.L().Error("长连接请求类型不存在")
  22. }
  23. }
  24. }

更改后的代码整体结构:

总结

代码并不是能跑就可以了,如果可以的话,请尽可能地优化自己的代码,让自己的代码利于维护和扩展,可读性高。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号