golang设计模式——享元模式

发布于:2023-01-20 ⋅ 阅读:(14) ⋅ 点赞:(0) ⋅ 评论:(0)

享元模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
}
➜ myproject go run main.go

创建房间1,玩家为66和88

初始化棋盘

玩家66的棋子为

兵,颜色为红,规则为过河前只能一步一步前进,过河后只能一步一步前进或者左右移位置为(1,1)

玩家88的棋子为

兵,颜色为黑,规则为过河前只能一步一步前进,过河后只能一步一步前进或者左右移位置为(16,1)

实例

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

代码

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
}

单元测试

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)
}

总结

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


网站公告

欢迎关注微信公众号

今日签到

点亮在社区的每一天
签到