设计模式 11

  • 创建型模式(5):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  • 结构型模式(7):适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式
  • 行为型模式(11):责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式

享元模式(Flyweight Pattern)

1 定义

享元模式的核心思想是将对象的状态分为内部状态(可以共享的部分)和外部状态(不能共享的部分)。通过共享相同的内部状态,减少内存的重复占用,从而实现系统的资源优化。

2 结构

享元模式的结构包含以下角色:

  • 享元(Flyweight): 定义享元对象的接口,通过外部状态完成享元对象的操作。
  • 具体享元(Concrete Flyweight): 实现享元接口,并存储可以共享的内部状态。
  • 非共享具体享元(Unshared Concrete Flyweight): 不可共享的享元类,通常是享元对象的组合。
  • 享元工厂(Flyweight Factory): 创建并管理享元对象,确保合理地共享对象。
  • 客户端(Client): 维护对所有享元对象的引用,并且需要将享元对象的外部状态传递给享元对象。

UML 类图

+-------------------+
|   IFlyweight      | 
+-------------------+
| + Operation()     |
+-------------------+
        ^
        |
+-----------------------+
|   ConcreteFlyweight   | 
+-----------------------+
| - property1           |       // 共享的内部状态
| - property2           |       // 共享的内部状态
| + Operation()         |
+-----------------------+

+-------------------+
| FlyweightFactory  | 
+-------------------+
| - flyweights      |
| + Operation()     |
+-------------------+

+---------------------------+
| UnsharedConcreteFlyweight | 
+---------------------------+
| - property1               |       // 不共享的外部状态
| - property2               |       // 不共享的外部状态
| - flyweight               |       // 组合享元
| + Operation()             |
+---------------------------+

3 示例代码

3.1 文字处理器

以下是一个实现享元模式的简单示例。在这个示例中,我们模拟了一个文字处理器,其中的字符对象可以被共享,以减少内存的占用。

享元接口

// 享元接口
public interface ICharacter
{
    void Display(int fontSize);
}

具体享元类

// 具体享元类:字符
public class Character : ICharacter
{
    private readonly char _symbol;  // 内部状态(共享部分)

    public Character(char symbol)
    {
        _symbol = symbol;
    }

    public void Display(int fontSize)
    {
        Console.WriteLine($"Character: {_symbol}, Font size: {fontSize}");
    }
}

享元工厂类

// 享元工厂类
public class CharacterFactory
{
    private readonly Dictionary<char, ICharacter> _characters = new Dictionary<char, ICharacter>();

    public ICharacter GetCharacter(char symbol)
    {
        if (!_characters.ContainsKey(symbol))
        {
            _characters[symbol] = new Character(symbol);  // 创建新的享元对象
        }

        return _characters[symbol];  // 返回共享的享元对象
    }
}

客户端代码

class Program
{
    static void Main(string[] args)
    {
        CharacterFactory factory = new CharacterFactory();

        // 获取并显示字符对象
        ICharacter a = factory.GetCharacter('A');
        a.Display(12);

        ICharacter b = factory.GetCharacter('B');
        b.Display(14);

        ICharacter a2 = factory.GetCharacter('A');
        a2.Display(18);

        // 检查两个 'A' 字符是否为同一个实例
        Console.WriteLine($"Is 'A' and 'A2' the same instance? {ReferenceEquals(a, a2)}");
    }
}

在这个例子中:

  • ICharacter 是享元接口,定义了 Display() 方法,用于显示字符及其大小。
  • Character 是具体享元类,存储了共享的字符符号 _symbol,并在 Display() 方法中使用外部状态(字体大小)。
  • CharacterFactory 是享元工厂类,负责创建和管理 Character 对象,并确保相同的字符符号只创建一个实例。
  • 客户端代码通过工厂获取字符对象,并在不同的字体大小下显示它们,同时检查同一字符是否被共享。
3.2 棋子与棋盘的实现示例

享元接口

// 享元接口:棋子
public interface IChessPiece
{
    void Display(int x, int y);
}

具体享元类

// 具体享元类:具体的棋子,如"黑车"或"白马"
public class ChessPiece : IChessPiece
{
    private readonly string _color;  // 内部状态(共享部分)
    private readonly string _type;   // 内部状态(共享部分)

    public ChessPiece(string color, string type)
    {
        _color = color;
        _type = type;
    }

    public void Display(int x, int y)
    {
        Console.WriteLine($"Chess Piece: {_color} {_type}, Position: ({x}, {y})");
    }
}

享元工厂类

// 享元工厂类:负责管理棋子对象
public class ChessPieceFactory
{
    private readonly Dictionary<string, IChessPiece> _pieces = new Dictionary<string, IChessPiece>();

    public IChessPiece GetChessPiece(string color, string type)
    {
        string key = color + "_" + type;
        if (!_pieces.ContainsKey(key))
        {
            _pieces[key] = new ChessPiece(color, type);
        }

        return _pieces[key];
    }
}

非共享具体享元类

// 非共享具体享元类:棋盘上的棋子
public class ChessBoardPosition
{
    private readonly int _x;  // 外部状态
    private readonly int _y;  // 外部状态
    private readonly IChessPiece _chessPiece;  // 共享享元

    public ChessBoardPosition(int x, int y, IChessPiece chessPiece)
    {
        _x = x;
        _y = y;
        _chessPiece = chessPiece;
    }

    public void Display()
    {
        _chessPiece.Display(_x, _y);
    }
}

客户端代码

class Program
{
    static void Main(string[] args)
    {
        ChessPieceFactory factory = new ChessPieceFactory();

        // 获取棋子,并在棋盘上设置位置
        ChessBoardPosition position1 = new ChessBoardPosition(1, 1, factory.GetChessPiece("Black", "Rook"));
        ChessBoardPosition position2 = new ChessBoardPosition(1, 2, factory.GetChessPiece("Black", "Knight"));
        ChessBoardPosition position3 = new ChessBoardPosition(1, 3, factory.GetChessPiece("Black", "Bishop"));
        ChessBoardPosition position4 = new ChessBoardPosition(1, 1, factory.GetChessPiece("White", "Pawn"));

        // 显示棋盘上的棋子
        position1.Display();
        position2.Display();
        position3.Display();
        position4.Display();
    }
}

在这个例子中:

  • 享元接口 IChessPiece: 定义了显示棋子的方法 Display(),要求提供棋子的位置信息。
  • 具体享元类 ChessPiece: 实现了 IChessPiece 接口,包含了棋子的颜色和类型,这些是共享的内部状态。
  • 享元工厂类 ChessPieceFactory: 负责创建和管理享元对象(棋子),确保同种颜色和类型的棋子只创建一个实例。
  • 非共享具体享元类 ChessBoardPosition: 代表棋盘上的每一个棋子位置,包含棋子在棋盘上的位置坐标 _x_y,这些是非共享的外部状态。每个位置持有一个共享的棋子对象。

4 特点

  • 优点:

    • 减少内存占用: 通过共享对象,显著减少了系统中的内存消耗,特别适用于大量相似对象的场景。

    • 提高性能: 减少了创建对象的开销,特别是在对象创建成本高昂的情况下。

  • 缺点:

    • 增加复杂性: 引入共享机制后,代码的复杂性增加,需要管理外部状态和内部状态。

    • 非线程安全: 享元对象在多线程环境下可能会引发线程安全问题,需要谨慎处理。

5 适用场景

  • 需要大量细粒度对象: 当系统中需要创建大量相似对象时,可以考虑使用享元模式来减少内存消耗。
  • 外部状态可分离: 当对象的状态可以分为内部状态和外部状态,且内部状态可以共享时,享元模式非常适合。

6 与其他模式的关系

  • 与单例模式的区别: 单例模式确保一个类只有一个实例,而享元模式允许多个实例共享内部状态。
  • 与工厂方法模式的区别: 工厂方法模式负责创建对象,而享元模式则关注共享对象的管理。

享元模式通过共享对象的内部状态,有效地减少了内存的占用,是优化系统性能的一种有效手段,特别是在需要大量相似对象的情况下。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部