Flyweight 享元模式简介与 C# 示例【结构型6】【设计模式来了_11】

发布时间 2023-11-01 20:12:19作者: 橙子家

〇、简介

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 策略模式对象。