文章

设计模式解密:享元模式的终极指南(PHP/Go双实现)

一、什么是享元模式?

享元模式(Flyweight Pattern) 是一种结构型设计模式,通过共享对象来最小化内存使用和对象创建成本。其核心思想是将对象的状态分为:

  • 内部状态(Intrinsic):可共享的恒定部分

  • 外部状态(Extrinsic):不可共享的可变部分

享元模式结构图


二、适用场景

  • ✅ 需要创建大量相似对象

  • ✅ 对象的大部分状态可以外部化

  • ✅ 内存占用是系统瓶颈

  • ✅ 需要缓存和复用对象

  • ✅ 游戏开发(如粒子系统、棋盘棋子)


三、PHP实现方案

1. 游戏子弹系统示例

// 享元接口
interface BulletType {
    public function render(int $x, int $y): string;
}

// 具体享元
class ConcreteBulletType implements BulletType {
    private $texture;
    private $color;

    public function __construct(string $texture, string $color) {
        $this->texture = $texture;
        $this->color = $color;
    }

    public function render(int $x, int $y): string {
        return "Rendering {$this->color} bullet with {$this->texture} at ($x, $y)";
    }
}

// 享元工厂
class BulletFactory {
    private static $bulletTypes = [];

    public static function getBulletType(string $texture, string $color): BulletType {
        $key = $texture . '_' . $color;
        if (!isset(self::$bulletTypes[$key])) {
            self::$bulletTypes[$key] = new ConcreteBulletType($texture, $color);
        }
        return self::$bulletTypes[$key];
    }

    public static function countTypes(): int {
        return count(self::$bulletTypes);
    }
}

// 客户端
class Bullet {
    private $type;
    private $x;
    private $y;

    public function __construct(BulletType $type, int $x, int $y) {
        $this->type = $type;
        $this->x = $x;
        $this->y = $y;
    }

    public function render(): string {
        return $this->type->render($this->x, $this->y);
    }
}

// 使用示例
$bullets = [];
$types = [
    ['metal', 'red'],
    ['plastic', 'blue'],
    ['metal', 'red'], // 重复类型
];

foreach ($types as $t) {
    $type = BulletFactory::getBulletType($t[0], $t[1]);
    $bullets[] = new Bullet($type, rand(0, 100), rand(0, 100));
}

foreach ($bullets as $bullet) {
    echo $bullet->render() . "\n";
}

echo "Total bullet types: " . BulletFactory::countTypes(); // 输出:2

四、Go实现方案

1. 文字编辑器字符优化示例

package main

import (
    "fmt"
    "sync"
)

// 享元接口
type CharacterStyle interface {
    Render(fontSize int) string
}

// 具体享元
type ConcreteCharacterStyle struct {
    fontFamily string
    color      string
}

func (c *ConcreteCharacterStyle) Render(fontSize int) string {
    return fmt.Sprintf("Font: %s, Color: %s, Size: %d", c.fontFamily, c.color, fontSize)
}

// 享元工厂
type CharacterStyleFactory struct {
    styles map[string]*ConcreteCharacterStyle
    mu     sync.Mutex
}

var factory = &CharacterStyleFactory{
    styles: make(map[string]*ConcreteCharacterStyle),
}

func (f *CharacterStyleFactory) GetStyle(fontFamily, color string) *ConcreteCharacterStyle {
    key := fontFamily + "|" + color

    f.mu.Lock()
    defer f.mu.Unlock()

    if style, exists := f.styles[key]; exists {
        return style
    }

    style := &ConcreteCharacterStyle{
        fontFamily: fontFamily,
        color:      color,
    }
    f.styles[key] = style
    return style
}

// 客户端
type Character struct {
    style    *ConcreteCharacterStyle
    char     rune
    fontSize int
}

func NewCharacter(char rune, fontSize int, fontFamily, color string) *Character {
    return &Character{
        style:    factory.GetStyle(fontFamily, color),
        char:     char,
        fontSize: fontSize,
    }
}

func (c *Character) Render() string {
    return fmt.Sprintf("%c: %s", c.char, c.style.Render(c.fontSize))
}

// 使用示例
func main() {
    chars := []*Character{
        NewCharacter('A', 12, "Arial", "Black"),
        NewCharacter('B', 14, "Times New Roman", "Red"),
        NewCharacter('C', 12, "Arial", "Black"), // 复用样式
    }

    for _, char := range chars {
        fmt.Println(char.Render())
    }

    fmt.Printf("Total styles created: %d\n", len(factory.styles)) // 输出:2
}

五、关键实现差异对比

特性

PHP

Go

线程安全

默认单线程无需处理

需使用sync.Mutex

对象存储

静态数组

map + 互斥锁

接口实现

显式implements

隐式接口实现

不可变对象

构造函数初始化

结构体字面量初始化

内存管理

引用计数GC

三色标记GC


六、模式优缺点分析

👍 优点:

  • 显著减少内存占用:共享重复状态

  • 提升性能:减少对象创建开销

  • 简化对象管理:集中存储共享状态

  • 支持大规模对象:适用于资源敏感场景

👎 缺点:

  • 增加代码复杂度:需要区分内外状态

  • 线程安全问题:共享对象需同步控制

  • 可能引入缓存膨胀:需合理控制缓存策略


七、实际应用案例

1. 游戏开发

  • 共享武器/角色皮肤属性

  • 复用粒子系统参数

2. 文档处理

  • 优化字符/段落样式存储

  • 复用富文本格式配置

3. GUI系统

  • 共享按钮/图标样式

  • 复用窗口主题配置

4. 电商系统

  • 商品SKU属性共享

  • 规格参数模板复用


八、与缓存策略的区别

享元模式

普通缓存

设计目标

优化对象存储

加速数据访问

管理内容

对象的部分状态

完整对象/数据

生命周期

与应用生命周期一致

可设置过期策略

访问方式

通过工厂获取

直接键值访问


九、最佳实践建议

  1. 严格区分状态:明确划分内部/外部状态

  2. 控制缓存大小:使用LRU等策略防止内存泄漏

  3. 不可变对象:共享对象应设为不可变

  4. 并发控制:多线程环境需同步机制

  5. 性能监控:统计缓存命中率优化策略


十、总结

享元模式是资源优化的艺术,通过共享不变的内在状态,它能显著降低系统内存消耗。无论是PHP的静态缓存还是Go的map+互斥锁实现,享元模式都为高并发、资源敏感场景提供了优雅的解决方案。

适用性检查清单

  • 系统需要创建大量相似对象

  • 内存占用是主要瓶颈

  • 对象的大部分状态可以外部化

  • 愿意增加一定代码复杂度换取性能提升

在下一篇文章中,我们将探讨 责任链模式 及其在请求处理流程中的应用。敬请期待!


下一篇预告:设计模式系列(十二)——责任链模式:请求处理的流水线

License:  CC BY 4.0