设计模式 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 与其他模式的关系
- 与单例模式的区别: 单例模式确保一个类只有一个实例,而享元模式允许多个实例共享内部状态。
- 与工厂方法模式的区别: 工厂方法模式负责创建对象,而享元模式则关注共享对象的管理。
享元模式通过共享对象的内部状态,有效地减少了内存的占用,是优化系统性能的一种有效手段,特别是在需要大量相似对象的情况下。