享元模式主要是为了复用对象,节省内存。使用享元模式需要有两个前提:
-
享元对象不可变:当享元模式创建出来后,它的变量和属性不会被修改
-
系统中存在大量重复对象:这些重复对象可以使用同一个享元,内存中只存在一份,这样会节省大量空间。当然这也是为什么享元对象不可变的原因,因为有很多引用,变更的话会引起很多问题。
UML类图位置:www.processon.com/diagraming/…
本文代码链接为:github.com/shidawuhen/…
1.定义
1.1享元模式
享元模式:运用共享技术有效的支持大量细粒度的对象。
UML:
1.2分析
享元模式主要是把系统中共同的、不变的对象抽象出来,达到共用一份的效果。
抽象出的对象接口为Flyweight,ConcreteFlyweight为实际被共享的对象。UnsharedConcreteFlyweight是否存在,主要看是否有对象是无需共享的。
享元模式里有工厂FlyweightFactory,主要是因为系统中需要的享元结构虽然确定了,但是享元的属性不同,所以需要管理多个对象,此处使用了工厂模式。关于工厂模式可以参看这篇文章Go设计模式(7)-工厂模式。
2.使用场景
享元模式还是有很多具体使用场景的,如很多联网类棋牌游戏。假设有100w场象棋游戏在同时进行,不使用享元模式的话,系统需要维护32*100w个象棋对象。但象棋的文案、颜色、规则是不变的,变的只是持有人和位置。所以将32个象棋对象抽象出来,当做享元,可以极大的节省空间,而且不会带来成本提升。
享元模式与其说是一种设计模式,不如说是一种设计理念,主要讲的是抽象的能力,将相同模块提取出来,供不同模块使用。从这个维度来说,代码重构中提取相同功能、单例模式等,何尝不是另一种享元。
3.代码实现
写一下象棋游戏中对于象棋的管理吧。
package main
import "fmt"
/**
* @Author: Jason Pang
* @Description: 棋子类,有文案、颜色、规则,这三种不变属性
*/
type Piece struct {
text string
color string
rule string
}
/**
* @Author: Jason Pang
* @Description: 棋子信息说明
* @receiver p
* @return string
*/
func (p *Piece) String() string {
return fmt.Sprintf("%s,颜色为%s,规则为%s", p.text, p.color, p.rule)
}
/**
* @Author: Jason Pang
* @Description: 棋子在棋盘位置
*/
type Pos struct {
x int64
y int64
}
/**
* @Author: Jason Pang
* @Description: 游戏中的棋子
*/
type GamePiece struct {
piece *Piece //棋子指针
pos Pos //棋子位置
ownerId int64 //玩家ID
roomId int64 //房间ID
}
/**
* @Author: Jason Pang
* @Description: 游戏中的棋子说明
* @receiver g
* @return string
*/
func (g *GamePiece) String() string {
return fmt.Sprintf("%s位置为(%d,%d)", g.piece, g.pos.x, g.pos.y)
}
/**
* @Author: Jason Pang
* @Description: 棋子工厂,包含32颗棋子信息
*/
type PieceFactory struct {
pieces []*Piece
}
/**
* @Author: Jason Pang
* @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 创建其它棋子。此处可以使用配置文件创建,能方便一些。系统中可以设置一个规则引擎,控制棋子运动。
}
/**
* @Author: Jason Pang
* @Description: 获取棋子信息
* @receiver f
* @param id
* @return *Piece
*/
func (f *PieceFactory) GetPiece(id int64) *Piece {
return f.pieces[id]
}
/**
* @Author: Jason Pang
* @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)
3总结
享元模式充分说明了抽象的重要性,希望大家能够善用这种模式,优化系统。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:shidawuhen.github.io/
往期文章回顾:
招聘
设计模式
语言
架构
存储
网络
工具
读书笔记
思考