• golang设计模式——享元模式


    享元模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-12agOiMK-1660312238028)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812204316525.png)]

    享元模式主要是为了复用对象,节省内存。使用享元模式需要有两个前提:

    1. 享元对象不可变:当享元模式创建出来后,它的变量和属性不会被修改
    2. 系统中存在大量重复对象:这些重复对象可以使用同一个享元,内存中只存在一份,这样会节省大量空间。当然这也是为什么享元对象不可变的原因,因为有很多引用,变更的话会引起很多问题。

    享元模式:运用共享技术有效的支持大量细粒度的对象。

    UML

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNwJgFKV-1660312238029)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812205357293.png)]

    分析

    享元模式主要是把系统中共同的、不变的对象抽象出来,达到共用一份的效果

    抽象出的对象接口为Flyweight,ConcreteFlyweight为实际被共享的对象。UnsharedConcreteFlyweight是否存在,主要看是否有对象是无需共享的。

    享元模式里有工厂FlyweightFactory,主要是因为系统中需要的享元结构虽然确定了,但是享元的属性不同,所以需要管理多个对象,此处使用了工厂模式。

    使用场景

    享元模式还是有很多具体使用场景的,如很多联网类棋牌游戏。假设有100w场象棋游戏在同时进行,不使用享元模式的话,系统需要维护32*100w个象棋对象。但象棋的文案、颜色、规则是不变的,变的只是持有人和位置。所以将32个象棋对象抽象出来,当做享元,可以极大的节省空间,而且不会带来成本提升。

    享元模式与其说是一种设计模式,不如说是一种设计理念,主要讲的是抽象的能力,将相同模块提取出来,供不同模块使用。从这个维度来说,代码重构中提取相同功能、单例模式等,何尝不是另一种享元。

    代码实现

    写一下象棋游戏中对于象棋的管理吧。

    package main
    
    import "fmt"
    
    /**
     * @Description: 棋子类,有文案、颜色、规则,这三种不变属性
     */
    type Piece struct {
       text  string
       color string
       rule  string
    }
    
    /**
     * @Description: 棋子信息说明
     * @receiver p
     * @return string
     */
    func (p *Piece) String() string {
       return fmt.Sprintf("%s,颜色为%s,规则为%s", p.text, p.color, p.rule)
    }
    
    /**
     * @Description: 棋子在棋盘位置
     */
    type Pos struct {
       x int64
       y int64
    }
    
    /**
     * @Description: 游戏中的棋子
     */
    type GamePiece struct {
       piece   *Piece //棋子指针
       pos     Pos    //棋子位置
       ownerId int64  //玩家ID
       roomId  int64  //房间ID
    }
    
    /**
     * @Description: 游戏中的棋子说明
     * @receiver g
     * @return string
     */
    func (g *GamePiece) String() string {
       return fmt.Sprintf("%s位置为(%d,%d)", g.piece, g.pos.x, g.pos.y)
    }
    
    /**
     * @Description: 棋子工厂,包含32颗棋子信息
     */
    type PieceFactory struct {
       pieces []*Piece
    }
    
    /**
     * @Description: 创建棋子。棋子的信息都是不变的
     * @receiver f
     */
    func (f *PieceFactory) CreatePieces() {
       f.pieces = make([]*Piece, 32)
       f.pieces[0] = &Piece{
          text:  "兵",
          color: "红",
          rule:  "过河前只能一步一步前进,过河后只能一步一步前进或者左右移",
       }
       f.pieces[1] = &Piece{
          text:  "兵",
          color: "黑",
          rule:  "过河前只能一步一步前进,过河后只能一步一步前进或者左右移",
       }
       //todo 创建其它棋子。此处可以使用配置文件创建,能方便一些。系统中可以设置一个规则引擎,控制棋子运动。
    }
    
    /**
     * @Description: 获取棋子信息
     * @receiver f
     * @param id
     * @return *Piece
     */
    func (f *PieceFactory) GetPiece(id int64) *Piece {
       return f.pieces[id]
    }
    
    /**
     * @Description: 初始化棋盘
     * @param roomId
     * @param u1
     * @param u2
     */
    func InitBoard(roomId int64, u1 int64, u2 int64, factory *PieceFactory) {
       fmt.Printf("创建房间%d,玩家为%d和%d \n", roomId, u1, u2)
       fmt.Println("初始化棋盘")
    
       fmt.Printf("玩家%d的棋子为 \n", u1)
       piece := &GamePiece{
          piece:   factory.GetPiece(0),
          pos:     Pos{1, 1},
          roomId:  roomId,
          ownerId: u1,
       }
       fmt.Println(piece)
    
       fmt.Printf("玩家%d的棋子为 \n", u2)
       piece2 := &GamePiece{
          piece:   factory.GetPiece(1),
          pos:     Pos{16, 1},
          roomId:  roomId,
          ownerId: u2,
       }
       fmt.Println(piece2)
    }
    func main() {
       factory := &PieceFactory{}
       factory.CreatePieces()
       InitBoard(1, 66, 88, factory)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    ➜ myproject go run main.go
    
    创建房间1,玩家为66和88
    
    初始化棋盘
    
    玩家66的棋子为
    
    兵,颜色为红,规则为过河前只能一步一步前进,过河后只能一步一步前进或者左右移位置为(1,1)
    
    玩家88的棋子为
    
    兵,颜色为黑,规则为过河前只能一步一步前进,过河后只能一步一步前进或者左右移位置为(16,1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实例

    我们现在正在做一个棋牌类的游戏,例如象棋,无论是什么对局,棋子的基本属性其实是固定的,并不会因为随着下棋的过程变化。那我们就可以把棋子变为享元,让所有的对局都共享这些对象,以此达到节省内存的目的。

    代码

    package flyweight
    
    var units = map[int]*ChessPieceUnit{
    	1: {
    		ID:    1,
    		Name:  "車",
    		Color: "red",
    	},
    	2: {
    		ID:    2,
    		Name:  "炮",
    		Color: "red",
    	},
    	// ... 其他棋子
    }
    
    // ChessPieceUnit 棋子享元
    type ChessPieceUnit struct {
    	ID    uint
    	Name  string
    	Color string
    }
    
    // NewChessPieceUnit 工厂
    func NewChessPieceUnit(id int) *ChessPieceUnit {
    	return units[id]
    }
    
    // ChessPiece 棋子
    type ChessPiece struct {
    	Unit *ChessPieceUnit
    	X    int
    	Y    int
    }
    
    // ChessBoard 棋局
    type ChessBoard struct {
    	chessPieces map[int]*ChessPiece
    }
    
    // NewChessBoard 初始化棋盘
    func NewChessBoard() *ChessBoard {
    	board := &ChessBoard{chessPieces: map[int]*ChessPiece{}}
    	for id := range units {
    		board.chessPieces[id] = &ChessPiece{
    			Unit: NewChessPieceUnit(id),
    			X:    0,
    			Y:    0,
    		}
    	}
    	return board
    }
    
    // Move 移动棋子
    func (c *ChessBoard) Move(id, x, y int) {
    	c.chessPieces[id].X = x
    	c.chessPieces[id].Y = y
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    单元测试

    package flyweight
    
    import (
    	"testing"
    
    	"github.com/stretchr/testify/assert"
    )
    
    func TestNewChessBoard(t *testing.T) {
    	board1 := NewChessBoard()
    	board1.Move(1, 1, 2)
    	board2 := NewChessBoard()
    	board2.Move(2, 2, 3)
    
    	assert.Equal(t, board1.chessPieces[1].Unit, board2.chessPieces[1].Unit)
    	assert.Equal(t, board1.chessPieces[2].Unit, board2.chessPieces[2].Unit)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    总结

    享元模式充分说明了抽象的重要性,希望大家能够善用这种模式,优化系统。

  • 相关阅读:
    软考——软件设计师中级2023年11月备考(1.计算机组成原理)
    练习3-7 成绩转换
    【十四】记一次MySQL宕机恢复过程,MySQL INNODB 损坏恢复
    js函数作用域
    从零到使用vue工程
    权威认可!安全狗获CNVD“漏洞信息报送贡献单位”殊荣
    Swift 周报 第十三期
    【Spring boot 文件上传】
    opencv dnn模块 示例(16) 目标检测 object_detection 之 yolov4
    elmentui表单重置及出现的问题
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126312015