设计模式 11 享元模式

设计模式 11

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

文章目录

  • [设计模式 11](#设计模式 11)
    • [享元模式(Flyweight Pattern)](#享元模式(Flyweight Pattern))
      • [1 定义](#1 定义)
      • [2 结构](#2 结构)
      • [3 示例代码](#3 示例代码)
        • [3.1 文字处理器](#3.1 文字处理器)
        • [3.2 棋子与棋盘的实现示例](#3.2 棋子与棋盘的实现示例)
      • [4 特点](#4 特点)
      • [5 适用场景](#5 适用场景)
      • [6 与其他模式的关系](#6 与其他模式的关系)

享元模式(Flyweight Pattern)

1 定义

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

2 结构

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

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

UML 类图

scss 复制代码
+-------------------+
|   IFlyweight      | 
+-------------------+
| + Operation()     |
+-------------------+
        ^
        |
+-----------------------+
|   ConcreteFlyweight   | 
+-----------------------+
| - property1           |       // 共享的内部状态
| - property2           |       // 共享的内部状态
| + Operation()         |
+-----------------------+

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

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

3 示例代码

3.1 文字处理器

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

享元接口

csharp 复制代码
// 享元接口
public interface ICharacter
{
    void Display(int fontSize);
}

具体享元类

csharp 复制代码
// 具体享元类:字符
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}");
    }
}

享元工厂类

csharp 复制代码
// 享元工厂类
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];  // 返回共享的享元对象
    }
}

客户端代码

csharp 复制代码
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 棋子与棋盘的实现示例

享元接口

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

具体享元类

csharp 复制代码
// 具体享元类:具体的棋子,如"黑车"或"白马"
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})");
    }
}

享元工厂类

csharp 复制代码
// 享元工厂类:负责管理棋子对象
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];
    }
}

非共享具体享元类

csharp 复制代码
// 非共享具体享元类:棋盘上的棋子
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);
    }
}

客户端代码

csharp 复制代码
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 与其他模式的关系

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

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

相关推荐
杨充15 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充16 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充16 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充16 小时前
2.面向对象的特性
设计模式
杨充16 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充16 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充16 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯19 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式