享元模式的核心思想是将对象的状态分为内部状态(可以共享的部分)和外部状态(不能共享的部分)。通过共享相同的内部状态,减少内存的重复占用,从而实现系统的资源优化。
享元模式的结构包含以下角色:
UML 类图
+-------------------+
| IFlyweight |
+-------------------+
| + Operation() |
+-------------------+
^
|
+-----------------------+
| ConcreteFlyweight |
+-----------------------+
| - property1 | // 共享的内部状态
| - property2 | // 共享的内部状态
| + Operation() |
+-----------------------+
+-------------------+
| FlyweightFactory |
+-------------------+
| - flyweights |
| + Operation() |
+-------------------+
+---------------------------+
| UnsharedConcreteFlyweight |
+---------------------------+
| - property1 | // 不共享的外部状态
| - property2 | // 不共享的外部状态
| - flyweight | // 组合享元
| + Operation() |
+---------------------------+
以下是一个实现享元模式的简单示例。在这个示例中,我们模拟了一个文字处理器,其中的字符对象可以被共享,以减少内存的占用。
享元接口
// 享元接口
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
对象,并确保相同的字符符号只创建一个实例。享元接口
// 享元接口:棋子
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
,这些是非共享的外部状态。每个位置持有一个共享的棋子对象。优点:
减少内存占用: 通过共享对象,显著减少了系统中的内存消耗,特别适用于大量相似对象的场景。
提高性能: 减少了创建对象的开销,特别是在对象创建成本高昂的情况下。
缺点:
增加复杂性: 引入共享机制后,代码的复杂性增加,需要管理外部状态和内部状态。
非线程安全: 享元对象在多线程环境下可能会引发线程安全问题,需要谨慎处理。
享元模式通过共享对象的内部状态,有效地减少了内存的占用,是优化系统性能的一种有效手段,特别是在需要大量相似对象的情况下。