本文主要介绍享元模式基础概念、实现原理和通过一个棋牌游戏例子简述实现过程。

享元模式

所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。 具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。

享元的实现并不复杂,通过工厂模式,通过一个 Map 来缓存已经创建过的享元对象,来达到复用的目的。 其中:

  • Flyweight : 享元类包含原始对象中部分能在多个对象中共享的状态。
  • FlyweightFactory:享元工厂,会对已有享元的缓存池进行管理。

接着,我们通过一个棋牌游戏的案例来演示。

案例-棋牌游戏

这里以象棋为例,游戏大厅中有N个房间,那么每个房间会有一个棋局对象,和X个(将、相、士、炮)棋子对象。以下是go的具体实现flyweight.go

package design_mode

type ChessPiece struct {
	Name      string
	Color     string
	PositionX int
	PositionY int
}

type ChessBoard struct {
	Cards map[int]*ChessPiece
}

func NewChessBoard() *ChessBoard {
	return &ChessBoard{
		Cards: map[int]*ChessPiece{
			1: {
				Name:      "車",
				Color:     "紅",
				PositionX: 1,
				PositionY: 11,
			},
			2: {
				Name:      "馬",
				Color:     "黑",
				PositionX: 2,
				PositionY: 2,
			},
		},
		// 其他棋子
	}
}


开N个房间flyweight_test.go

package design_mode

import (
	"testing"
)

func TestNewChessBoard(t *testing.T) {
	game1 := NewChessBoard()
	game2 := NewChessBoard()
	t.Log(game1.Cards[1])
	t.Log(game2.Cards[1])
	t.Log(game1.Cards[1] == game2.Cards[1])
}

OutPut:

=== RUN   TestNewChessBoard
    flyweight_test.go:15: &{車 紅 1 11}
    flyweight_test.go:16: &{車 紅 1 11}
    flyweight_test.go:17: false
--- PASS: TestNewChessBoard (0.00s)

例中,每创建一个棋局就需要初始化对应的棋子,如果游戏大厅有百万人同时在线 ,那保存这么多棋局对象就会消耗大量的内存。

使用享元模式重构

如何对上例进行优化呢,从例子中,每个大厅存在大量重复对象棋牌,利用享元模式把棋牌设计成共享的(map)详细如下。

package design_mode

import "fmt"

var chessPieceUnit = map[int]*ChessPiece{
	1: {
		Name:  "車",
		Color: "紅",
		PositionX: 1,
		PositionY: 11,
	},
	2: {
		Name:  "馬",
		Color: "黑",
		PositionX: 2,
		PositionY: 2,
	},
	// 其他棋子
}

func NewChessPieceUnitFactory() *ChessBoard {
	board := &ChessBoard{Cards: map[int]*ChessPiece{}}
	for id := range chessPieceUnit {
		board.Cards[id] = chessPieceUnit[id]
	}
	return board
}

output:

=== RUN   TestNewChessPieceUnitFactory
    flyweight2_test.go:13: &{  1 11}
    flyweight2_test.go:14: &{  1 11}
    flyweight2_test.go:15: true
--- PASS: TestNewChessPieceUnitFactory (0.00s)

例中,通过一个ChessPieceUnit来缓存一个ChessBoard棋局的30+个ChessPiece棋子对象。原本如果是1万个ChessBoard棋局,需要创建【1万乘30】约30万个棋子对象。现在通过共享同一个ChessPieceUnit,那么需要创建约【30】个对象,大大节省了内存。

参考