一、引言
在软件开发过程中,我们常常面临着创建大量细粒度对象的情况,这可能会导致内存占用过高、性能下降等问题。享元模式(Flyweight Pattern)就像是一位空间管理大师,它能够在不影响功能的前提下,有效地减少对象的数量,从而优化系统资源的使用。
二、定义与描述
享元模式是一种结构型设计模式,它主要用于通过共享尽可能多的相似对象来减少内存使用和提高性能。其核心思想是将对象的状态分为内部状态(intrinsic state)和外部状态(extrinsic state)。内部状态是对象可共享的部分,它不会随环境的改变而改变;外部状态则是随环境变化而变化的部分,不能被共享。
三、抽象背景
假设我们正在开发一个游戏,游戏中有许多相同类型的怪物,这些怪物可能只有一些属性(如生命值、攻击力等)的差异。如果我们为每个怪物都创建一个独立的对象,那么随着怪物数量的增加,内存的消耗将变得非常大。享元模式就可以用来解决这个问题,将怪物的通用属性(如外观、基本行为等内部状态)进行共享,而将每个怪物特有的属性(如当前生命值、当前攻击力等外部状态)单独处理。
四、适用场景与现实问题解决
- 图形绘制系统
- 在图形绘制系统中,可能需要绘制大量相同类型的图形,如圆形、矩形等。这些图形的形状(内部状态)是固定的,但它们的位置、颜色(外部状态)可能不同。通过享元模式,可以共享图形的形状对象,减少内存占用。
- 文档编辑器中的字符处理
- 文档编辑器中有大量的字符,每个字符的字体样式、大小等属性可能不同,但字符的基本形状(内部状态)是相同的。享元模式可以用来共享字符的基本形状对象。
五、享元模式的现实生活的例子
- 汽车租赁公司
- 汽车租赁公司有多种类型的汽车可供租赁,如轿车、SUV等。每一种类型的汽车(内部状态)是固定的,包括车型、座位数等。而汽车的颜色、当前里程数(外部状态)是随每一次租赁而变化的。租赁公司可以将相同类型的汽车看作是享元对象,共享汽车的基本信息,从而更好地管理车辆资源。
- 咖啡店的咖啡杯
- 咖啡店有不同种类的咖啡杯,如拿铁杯、卡布奇诺杯等。杯子的形状(内部状态)是固定的,但是杯子里咖啡的量、是否加糖(外部状态)是不同的。咖啡店可以将相同类型的杯子看作享元对象,共享杯子的基本形状信息。
六、初衷与问题解决
初衷是为了减少内存中对象的数量,提高系统的性能和资源利用率。通过共享内部状态,避免了创建大量重复的对象,从而解决了因对象数量过多导致的内存占用过大和性能下降的问题。
七、代码示例
Java示例
类图:
FlyweightFactory
类有一个私有属性flyweights
(类型为Map<String, Flyweight>
)用于存储享元对象,并且有getFlyweight
方法,根据传入的key
来获取或创建具体的享元对象。Flyweight
是抽象类,有受保护的属性key
,构造方法以及抽象方法operation
,定义了享元对象的基本结构和行为规范。ConcreteFlyweight
类继承自Flyweight
类,实现了自己的构造方法,并覆写了operation
方法,用于提供具体的享元行为实现。
流程图:
首先创建 FlyweightFactory
对象,然后两次调用 getFlyweight
方法来获取享元对象(第二次调用时会复用第一次创建的对象,因为已经存在对应 key
的对象了),最后分别调用获取到的享元对象的 operation
方法来执行具体操作。
代码:
import java.util.HashMap;
import java.util.Map;
// 享元工厂类
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(key));
}
return flyweights.get(key);
}
}
// 抽象享元类
abstract class Flyweight {
protected String key;
public Flyweight(String key) {
this.key = key;
}
abstract void operation();
}
// 具体享元类
class ConcreteFlyweight extends Flyweight {
public ConcreteFlyweight(String key) {
super(key);
}
@Override
void operation() {
System.out.println("具体享元 " + key + " 被调用");
}
}
public class Main {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("A");
Flyweight flyweight2 = factory.getFlyweight("A");
flyweight1.operation();
flyweight2.operation();
}
}
C++示例
#include <iostream>
#include <unordered_map>
// 抽象享元类
class Flyweight {
public:
virtual void operation() = 0;
virtual ~Flyweight() {}
};
// 具体享元类
class ConcreteFlyweight : public Flyweight {
private:
std::string key;
public:
ConcreteFlyweight(std::string key) : key(key) {}
void operation() override {
std::cout << "具体享元 " << key << " 被调用" << std::endl;
}
};
// 享元工厂类
class FlyweightFactory {
private:
std::unordered_map<std::string, Flyweight*> flyweights;
public:
Flyweight* getFlyweight(std::string key) {
if (flyweights.find(key) == flyweights.end()) {
flyweights[key] = new ConcreteFlyweight(key);
}
return flyweights[key];
}
~FlyweightFactory() {
for (auto it : flyweights) {
delete it.second;
}
}
};
int main() {
FlyweightFactory factory;
Flyweight* flyweight1 = factory.getFlyweight("A");
Flyweight* flyweight2 = factory.getFlyweight("A");
flyweight1->operation();
flyweight2->operation();
return 0;
}
Python示例
class FlyweightFactory:
def __init__(self):
self.flyweights = {}
def get_flyweight(self, key):
if key not in self.flyweights:
self.flyweights[key] = ConcreteFlyweight(key)
return self.flyweights[key]
class Flyweight:
def __init__(self, key):
self.key = key
def operation(self):
pass
class ConcreteFlyweight(Flyweight):
def operation(self):
print(f"具体享元 {self.key} 被调用")
if __name__ == "__main__":
factory = FlyweightFactory()
flyweight1 = factory.get_flyweight("A")
flyweight2 = factory.get_flyweight("A")
flyweight1.operation()
flyweight2.operation()
Go示例
package main
import (
"fmt"
)
// 抽象享元接口
type Flyweight interface {
operation()
}
// 具体享元结构体
type ConcreteFlyweight struct {
key string
}
func (cf *ConcreteFlyweight) operation() {
fmt.Printf("具体享元 %s 被调用\n", cf.key)
}
// 享元工厂结构体
type FlyweightFactory struct {
flyweights map[string]Flyweight
}
func NewFlyweightFactory() *FlyweightFactory {
return &FlyweightFactory{
flyweights: make(map[string]Flyweight),
}
}
func (ff *FlyweightFactory) getFlyweight(key string) Flyweight {
if _, ok := ff.flyweights[key];!ok {
ff.flyweights[key] = &ConcreteFlyweight{key}
}
return ff.flyweights[key]
}
func main() {
factory := NewFlyweightFactory()
flyweight1 := factory.getFlyweight("A")
flyweight2 := factory.getFlyweight("A")
flyweight1.operation()
flyweight2.operation()
}
八、享元模式的优缺点
-
优点
- 减少内存占用:通过共享对象,大大减少了创建对象所需的内存空间,特别是在处理大量相似对象时效果显著。
- 提高性能:减少了对象的创建和销毁操作,从而提高了系统的运行速度。
- 易于维护:将对象的内部状态和外部状态分离,使得代码结构更加清晰,易于理解和维护。
-
缺点
- 增加复杂性:需要额外的代码来管理享元对象的创建、共享和维护,这可能会增加系统的复杂性。
- 外部状态管理:外部状态的处理需要额外的设计考虑,如果处理不当可能会导致逻辑混乱。
九、享元模式的升级版
一种常见的升级版是组合享元模式(Composite Flyweight Pattern)。在这种模式下,享元对象可以组合成更复杂的结构。例如,在图形绘制系统中,不仅可以共享单个图形(如圆形、矩形)的享元对象,还可以将这些享元对象组合成更复杂的图形(如由多个圆形和矩形组成的复杂图案),而这个复杂图案本身也可以作为一个享元对象被共享。这样可以进一步提高系统的灵活性和资源利用率。
思维导图: