〇、简介
1、什么是享元模式?
一句话解释:
** 将相似或同类的对象共享同一个对象,将这些对象暂存在列表中,使用时直接取出,避免每次使用时都要新建浪费资源。**
享元模式的目的是减少对象的创建,通过共享对象来提高系统的性能。享元设计模式将对象的实例分为两种:内部共享对象和外部共享对象。内部共享对象是由享元类创建的,可以被多个对象共享。外部共享对象是由客户端创建的,不能被多个对象共享。
官方意图描述:运用共享技术有效地支持大量细粒度的对象。
一个比喻:(学校的足球场和篮球馆)
** 首先对于学校的足球场和篮球馆有共享属性,当有足球比赛时,就对应的是足球场,有篮球比赛时,对应的就是篮球馆,不会每次比赛都去新建场馆。**
2、享元模式的优缺点和适用场景
优点:
- 减少对象的创建:享元设计模式通过共享对象来减少对象的创建,这样可以减少内存占用从而提高系统的性能。
- 简化客户端代码:客户端只需要与享元接口交互,而不需要了解底层的具体实现细节,从而简化了客户端的代码。
- 提高系统的扩展性:享元设计模式通过共享对象来提高系统的扩展性,这样可以方便地添加新的共享对象。
- 降低系统的耦合度:享元模式将对象的创建和使用分离开来,使得系统的各个部分之间的耦合度降低,提高了系统的可维护性和可扩展性。
缺点:
- 增加了系统的复杂性:享元模式需要引入额外的享元工厂类和享元类,增加了系统的复杂性。
- 可能增加系统的维护成本:享元模式可能会增加系统的维护成本,因为需要考虑如何更新对象、如何维护对象、如何测试对象等问题。
- 影响系统的稳定性:当新增类型时,需要对享元工厂进行修改,可能导致系统的异常,同时也会增加额外的维护成本。
适用场景:
- 对象池:当需要频繁创建和销毁相似的对象时,可以使用享元模式将这些对象缓存在一个池中,以便重复使用。这样可以减少对象的创建和销毁成本,提高性能。
- 文字编辑器:在文字编辑器中,每个字符都可以作为一个独立的对象来表示。但对于相同的字符,我们不必为每个出现的位置都创建一个新对象,而是可以共享同一个对象实例,从而节省内存空间。
- 缓存系统:在缓存系统中,经常需要缓存大量的数据对象。通过使用享元模式,可以共享相同的数据对象,减少内存占用,并提高缓存的效率。
- 游戏开发:在游戏中,特别是大规模多人在线游戏(MMOG)中,存在大量相似的对象,如玩家、怪物、道具等。通过使用享元模式,可以共享这些相似对象之间的公共数据,并且只需存储各个对象的变化部分,从而节省内存占用。
一、通过简单的示例代码实现享元模式
如下示例代码,通过一个字典来存储类的实例,如果 key 标识的实例第二次调用时,就直接从字典中取,不再重新创建:
// 测试一下
class Program
{
static void Main(string[] args)
{
Flyweight flyweight = FlyweightFactory.GetFlyweight("Flyweight");
Flyweight flyweight1 = FlyweightFactory.GetFlyweight("Flyweight1");
Flyweight flyweight2 = FlyweightFactory.GetFlyweight("Flyweight2");
flyweight.Operation();
flyweight1.Operation();
flyweight2.Operation();
Console.ReadLine();
}
}
public class FlyweightFactory
{
private static Dictionary<string, Flyweight> flyweightDictionary = new Dictionary<string, Flyweight>();
public static Flyweight GetFlyweight(string key)
{
if (flyweightDictionary.ContainsKey(key))
{
return flyweightDictionary[key];
}
else
{
switch (key)
{
case "Flyweight1":
ConcreteFlyweight1 flyweight1 = new ConcreteFlyweight1();
flyweightDictionary[key] = flyweight1;
return flyweight1;
case "Flyweight2":
ConcreteFlyweight2 flyweight2 = new ConcreteFlyweight2();
flyweightDictionary[key] = flyweight2;
return flyweight2;
default:
Flyweight flyweight = new Flyweight();
flyweightDictionary[key] = flyweight;
return flyweight;
}
}
}
}
public class Flyweight
{
public virtual void Operation()
{
Console.WriteLine("Operation is performed by Flyweight.");
}
}
public class ConcreteFlyweight1 : Flyweight
{
public override void Operation()
{
Console.WriteLine("ConcreteFlyweight1 is performing the operation.");
}
}
public class ConcreteFlyweight2 : Flyweight
{
public override void Operation()
{
Console.WriteLine("ConcreteFlyweight2 is performing the operation.");
}
}
若后续需要添加新的享元实例ConcreteFlyweight3
,就可以直接继承类Flyweight
,但同时也需要在FlyweightFactory
工厂类中添加对应的实例判断代码。
二、结构
根据上一章节中的示例代码,可以得结构图:
Flyweight:描述一个接口,通过这个接口 flyweight 可以接受并作用于外部状态。
ConcreteFlyweight:实现 Flyweight 接口,并为内部状态(如果有的话)增加存储空间。Concrete-Flyweight 对象是可共享的。它所存储的状态必须是内部的,即它必须独立于 ConcreteFlyweight 对象的场景。
FlyweightFactory:创建并管理flyweight 对象;确保合理地共享 flyweight。当用户请求一个 flyweight 时,FlyweightFactory 对象提供一个已创建的实例或者创建一个(如果不存在的话)。
Client:维持一个对 flyweight 的引用;计算或存储一个或多个 flyweight 的外部状态。
三、相关模式
Flyweight 享元模式通常和 Composite 组合模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。
通常,最好用 flyweight 实现 State 状态模式和Strategy 策略模式对象。