设计模式(分类) 设计模式(六大原则)
创建型(5种) 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式
结构型(7种) 适配器模式 装饰器模式 代理模式 外观模式 桥接模式 组合模式 享元模式
行为型(11种) 策略模式 模板方法模式 观察者模式 迭代器模式 责任链模式 命令模式
备忘录模式 状态模式 访问者模式 中介者模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享已存在的类似对象来有效支持大量细粒度对象的复用,从而降低系统内存占用并提高性能。它适用于对象的大部分状态都可以被外部化,而只有少量内在状态(共享部分)需要存储在享元对象中。这种模式通过共享内部状态相同的对象,避免了大量相似对象的创建。
模式结构
享元模式主要包含以下角色:
Flyweight(抽象享元类):
- 定义了享元对象的公共接口,通常包括共享状态的访问方法和非共享状态的操作方法。
- 提供一个或多个工厂方法,用于创建或获取享元对象。
ConcreteFlyweight(具体享元类):
- 实现抽象享元类的接口,存储享元对象的内部状态(共享部分)。
- 提供操作内部状态的方法。
UnsharedConcreteFlyweight(非共享具体享元类,可选):
- 对于某些不适合共享的状态,可以创建非共享的具体享元类来处理。
FlyweightFactory(享元工厂):
- 负责创建和管理享元对象,确保享元对象的唯一性。
- 提供一个方法来获取享元对象,如果该对象已存在,则直接返回已创建的享元对象;否则创建一个新的享元对象并返回。
Client(客户端):
- 通过享元工厂请求享元对象。
- 向享元对象传递外部状态(非共享部分)。
工作原理
- 客户端:通过享元工厂请求享元对象,并向对象传递必要的外部状态。
- FlyweightFactory:根据客户端请求创建或查找已存在的享元对象,并返回给客户端。
- ConcreteFlyweight:存储并操作内部状态,对外部状态作出响应。
优缺点
优点
- 减少对象创建数量:通过共享内部状态相同的对象,显著减少了系统中对象的数量,节省内存。
- 提高性能:由于对象数量减少,对象的创建、销毁和垃圾回收等操作的开销也相应减小。
- 支持大量细粒度对象:对于需要创建大量相似对象的场景,享元模式能有效应对。
缺点
- 增加复杂性:引入享元模式后,系统需要管理享元对象的创建、共享和外部状态传递,增加了设计和实现的复杂性。
- 外部状态管理:客户端需要负责传递外部状态给享元对象,这可能使得客户端代码与享元对象的交互变得复杂。
- 不适合所有场景:并非所有对象都能被共享。只有当对象的大部分状态可以被外部化时,享元模式才适用。
适用场景
- 对象数量巨大且内部状态大部分相同:当系统需要处理大量相似对象,且这些对象的大部分状态是共享的,可以考虑使用享元模式。
- 内存消耗成为瓶颈:当内存资源有限,对象创建和存储的成本较高时,通过享元模式减少对象数量可以改善性能。
- 对象的创建成本高于使用成本:如果创建一个对象比多次复用该对象更耗时或耗资源,享元模式可以减少创建开销。
代码示例(以Java为例)
java
// 抽象享元类
interface Character {
void display(char symbol, int x, int y);
}
// 具体享元类
class CharacterImpl implements Character {
private char symbol;
public CharacterImpl(char symbol) {
this.symbol = symbol;
}
@Override
public void display(char symbol, int x, int y) {
System.out.println("Displaying character '" + symbol + "' at (" + x + ", " + y + ")");
// 这里假设实际操作是绘制字符到屏幕上,但实际位置由外部状态(x, y)决定
}
}
// 享元工厂
class CharacterFactory {
private Map<Character, CharacterImpl> flyweights = new HashMap<>();
public Character getCharacter(char symbol) {
if (!flyweights.containsKey(symbol)) {
flyweights.put(symbol, new CharacterImpl(symbol));
}
return flyweights.get(symbol);
}
}
// 客户端代码
public class FlyweightPatternDemo {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();
Character a = factory.getCharacter('A');
Character b = factory.getCharacter('B');
a.display('A', 10, 10);
b.display('B', 20, 20);
// 即使多次请求相同的字符,也不会创建新的对象
Character anotherA = factory.getCharacter('A');
assert a == anotherA; // 指向同一享元对象
}
}
代码示例(以 Python为例)
使用Python实现享元模式的一个示例,我们将创建一个简单的字符渲染系统,其中每个字符都是一个享元对象,它们的内部状态是字符本身,而外部状态则是渲染时的位置(x, y坐标):
python
from typing import Dict, Tuple
import weakref
class Character:
"""抽象享元类"""
def __init__(self, symbol: str):
self._symbol = symbol
def display(self, position: Tuple[int, int]):
"""显示字符,外部状态由客户端传递"""
print(f"Displaying character '{self._symbol}' at {position}")
class CharacterFactory:
"""享元工厂类"""
_flyweights: Dict[str, weakref.ReferenceType[Character]] = {}
@classmethod
def get_character(cls, symbol: str) -> Character:
"""
获取或创建字符享元对象。
如果字符享元已经存在,则直接返回;否则创建新的享元对象并添加到缓存中。
"""
flyweight_ref = cls._flyweights.get(symbol)
if flyweight_ref is not None and flyweight_ref() is not None:
return flyweight_ref()
flyweight = Character(symbol)
cls._flyweights[symbol] = weakref.ref(flyweight)
return flyweight
def main():
factory = CharacterFactory()
# 请求并显示两个不同的字符
a = factory.get_character('A')
a.display((10, 10))
b = factory.get_character('B')
b.display((20, 20))
# 再次请求相同的字符,应当返回已存在的享元对象
another_a = factory.get_character('A')
assert a is another_a
print("Characters displayed using the Flyweight pattern.")
if __name__ == "__main__":
main()
在这个Python示例中:
Character
类作为抽象享元类,定义了享元对象的公共接口(display
方法),并存储了字符的内部状态(字符符号)。CharacterFactory
类作为享元工厂,使用一个字典来缓存已创建的享元对象。字典的键是字符符号,值是弱引用(weakref
)以避免循环引用导致内存泄漏。当客户端请求某个字符时,如果该字符的享元对象已存在,则直接返回;否则创建一个新的享元对象,添加到字典中,并返回该对象的弱引用。main
函数展示了客户端如何使用享元工厂来获取字符享元对象,并通过display
方法传递外部状态(显示位置)。当请求相同的字符时,工厂会返回同一个享元对象,证明了享元模式的有效性。
运行此代码,将会看到字符在指定位置被正确显示,且两次请求相同的字符时,返回的是同一个对象实例。这就是享元模式在Python中的应用。