在学习了外观模式如何简化复杂子系统的接口后,我们来探讨享元模式。享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
“运用共享技术有效地支持大量细粒度的对象。” (Use sharing to support large numbers of fine-grained objects efficiently.)
想象一下你在开发一个文字处理器。文档中可能包含成千上万个字符。如果为每个字符(如 ‘a’, ‘b’, ‘c’ 等)都创建一个独立的对象,并且每个对象都存储其字形、字体、大小等信息,那么内存消耗会非常巨大。
享元模式通过共享那些状态不随上下文变化的对象来解决这个问题。对于字符来说:
享元工厂会缓存并复用具有相同内部状态的字符对象。当需要一个字符时,客户端向工厂请求,工厂检查缓存中是否已有该字符,有则返回,无则创建、缓存并返回。
享元模式的主要目的:
围棋棋子:
自行车租赁:
印刷厂的铅字:
Java 中的 String
常量池:
String s1 = "abc"; String s2 = "abc";
,Java 会检查字符串常量池。如果 “abc” 已经存在,s2
会直接引用池中的对象,而不是创建一个新的。这里的 “abc” 就是一个享元对象。享元模式通常包含以下角色:
FlyweightFactory
请求。FlyweightFactory
检查其缓存(如一个 Map
)中是否已存在具有该键的享元对象。
ConcreteFlyweight
对象,用传入的键(或其对应的内部状态)初始化它,将其存入缓存,然后返回给客户端。operation()
方法时,需要传入外部状态。operation()
方法利用传入的外部状态和自身的内部状态来完成操作。UnsharedConcreteFlyweight
对象不由工厂管理,客户端可以直接创建和使用它们,它们通常用于那些不能完全共享的情况。
享元模式在以下所有条件都满足时可以考虑使用:
例如:文本编辑器中的字符、图形系统中的图形元素(如点、线)、游戏中的粒子效果或大量重复的NPC(非玩家角色)等。
优点:
缺点:
让我们以绘制不同颜色的圆形为例。圆形的“形状是圆”是内部状态,而圆心坐标、半径和颜色是外部状态。为了简化,我们假设“颜色”也是内部状态,而坐标和半径是外部状态。
// shape.go (Flyweight interface)
package drawing
// Shape 享元接口
type Shape interface {
Draw(x, y, radius int) // 外部状态: x, y, radius
GetColor() string // 内部状态的一部分,用于工厂的key
}
// Shape.java (Flyweight interface)
package com.example.drawing.flyweight;
// 享元接口
public interface Shape {
void draw(int x, int y, int radius); // 外部状态: x, y, radius
String getColor(); // 内部状态的一部分,用于工厂的key
}
// circle.go (ConcreteFlyweight)
package drawing
import "fmt"
// Circle 具体享元类
type Circle struct {
color string // 内部状态
}
func NewCircle(color string) *Circle {
fmt.Printf("Creating Circle of color: %s\n", color) // 观察创建过程
return &Circle{color: color}
}
func (c *Circle) GetColor() string {
return c.color
}
func (c *Circle) Draw(x, y, radius int) {
fmt.Printf("Drawing a %s circle at (%d, %d) with radius %d\n", c.color, x, y, radius)
}
// Circle.java (ConcreteFlyweight)
package com.example.drawing.flyweight;
// 具体享元类
public class Circle implements Shape {
private final String color; // 内部状态 (final 确保不可变)
public Circle(String color) {
System.out.println("Creating Circle of color: " + color); // 观察创建过程
this.color = color;
}
@Override
public String getColor() {
return color;
}
@Override
public void draw(int x, int y, int radius) {
System.out.printf("Drawing a %s circle at (%d, %d) with radius %d%n", color, x, y, radius);
}
}
// shape_factory.go (FlyweightFactory)
package drawing
import (
"fmt"
"sync"
)
// ShapeFactory 享元工厂
type ShapeFactory struct {
circleMap map[string]*Circle // Go的map不是线程安全的,实际应用需加锁
lock sync.Mutex
}
var factoryInstance *ShapeFactory
var once sync.Once
// GetShapeFactory 获取工厂单例 (线程安全)
func GetShapeFactory() *ShapeFactory {
once.Do(func() {
factoryInstance = &ShapeFactory{
circleMap: make(map[string]*Circle),
}
})
return factoryInstance
}
// GetCircle 根据颜色获取圆形享元对象
func (sf *ShapeFactory) GetCircle(color string) *Circle {
sf.lock.Lock()
defer sf.lock.Unlock()
circle, exists := sf.circleMap[color]
if !exists {
circle = NewCircle(color) // 创建新的享元对象
sf.circleMap[color] = circle
}
return circle
}
func (sf *ShapeFactory) GetTotalCirclesCreated() int {
sf.lock.Lock()
defer sf.lock.Unlock()
return len(sf.circleMap)
}
// ShapeFactory.java (FlyweightFactory)
package com.example.drawing.factory;
import com.example.drawing.flyweight.Circle;
import com.example.drawing.flyweight.Shape;
import java.util.HashMap;
import java.util.Map;
// 享元工厂
public class ShapeFactory {
// 使用HashMap存储享元对象,key为颜色
private static final Map<String, Shape> circleMap = new HashMap<>();
// 根据颜色获取圆形享元对象
// synchronized 保证线程安全,或者使用 ConcurrentHashMap
public static synchronized Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color); // 创建新的享元对象
circleMap.put(color, circle); // 存入池中
System.out.println("Putting circle with color " + color + " into cache.");
}
return circle;
}
public static synchronized int getTotalCirclesCreated() {
return circleMap.size();
}
}
// main.go (示例用法)
/*
package main
import (
"./drawing"
"fmt"
"math/rand"
"time"
)
var colors = []string{"Red", "Green", "Blue", "Yellow", "Black"}
func main() {
rand.Seed(time.Now().UnixNano())
factory := drawing.GetShapeFactory()
fmt.Println("--- Client: Drawing 20 circles ---")
for i := 0; i < 20; i++ {
color := colors[rand.Intn(len(colors))] // 随机选择颜色
circle := factory.GetCircle(color) // 从工厂获取享元对象
x := rand.Intn(100)
y := rand.Intn(100)
radius := rand.Intn(50) + 5
circle.Draw(x, y, radius) // 传递外部状态进行绘制
}
fmt.Printf("\nTotal distinct circle objects created: %d\n", factory.GetTotalCirclesCreated())
// 尽管绘制了20次,但实际创建的Circle对象数量最多为颜色种类数 (5)
}
*/
// Main.java (示例用法)
/*
package com.example;
import com.example.drawing.factory.ShapeFactory;
import com.example.drawing.flyweight.Shape;
import java.util.Random;
public class Main {
private static final String[] colors = {"Red", "Green", "Blue", "Yellow", "Black"};
private static final Random random = new Random();
public static void main(String[] args) {
System.out.println("--- Client: Drawing 20 circles ---");
for (int i = 0; i < 20; ++i) {
String color = getRandomColor(); // 随机选择颜色
Shape circle = ShapeFactory.getCircle(color); // 从工厂获取享元对象
int x = getRandomCoordinate();
int y = getRandomCoordinate();
int radius = getRandomRadius();
circle.draw(x, y, radius); // 传递外部状态进行绘制
}
System.out.printf("%nTotal distinct circle objects created: %d%n", ShapeFactory.getTotalCirclesCreated());
// 尽管绘制了20次,但实际创建的Circle对象数量最多为颜色种类数 (5)
}
private static String getRandomColor() {
return colors[random.nextInt(colors.length)];
}
private static int getRandomCoordinate() {
return random.nextInt(100);
}
private static int getRandomRadius() {
return random.nextInt(50) + 5;
}
}
*/
享元模式是一种优化模式,通过共享对象来减少内存使用和提高性能,特别适用于系统中存在大量相似的细粒度对象的情况。它将对象的状态分为内部状态(可共享)和外部状态(由客户端提供),并使用工厂来管理和复用享元对象。虽然它会增加一些实现的复杂性,但在合适的场景下,其带来的资源节省效益是显著的。
记住它的核心:共享对象,分离内外状态,减少内存占用。