通俗易懂设计模式解析——享元模式

  • 2019 年 10 月 3 日
  • 筆記

前言

   今天我们继续讲述设计模式,今天提及的是享元模式,享——共享。之前不是出现了一系列共享的东西吗?为啥呀,还不就是有些东西每个人都需要,但是每个人都去买一个又有点浪费。所以出现共享。话费一定的经济可以使用,使用完成之后又归还。这就是享。分享共享。今天讲的享元模式跟这相类似。享元模式——通俗来说也就是共享最小单元的一种模式。我们就一起看看到底啥是这享元模式吧。

享元模式介绍

一、来由

  在软件系统开发中,我们肯定会出现重复使用同一个对象的情况,每次使用都new一个对象出来。这样的话对于内存来说就需要多次反复的申请内存了。这样使用内存也就越来越多,这会出现大问题的。那么能不能创建new一个对象。然后使用的时候就共同使用一个就好了。这也就是享元模式的含义所在了——共享一个对象。

二、 意图

  运用共享技术有效地支持大量细粒度的对象。

三、 案例图

 

 

 

 

四、享元模式代码示例

   看上面的案例图我们可以发现享元模式主要包含以下部分:

享元工厂角色:这个角色主要负责创建和管理享元角色。判断是否存在符合要求的享元对象,如果存在则直接拿取,如果不存在的话就会创建一个享元对象并保存。

抽象享元角色:这个角色是所有享元角色的基类。提供需要实现的公共接口

具体享元角色:继承于抽象享元角色。实现其抽象的接口。

客户端:负责调用并处理逻辑,且保存多有享元对象的状态

  在我们平时使用的编辑器中,会出现很多的字,这些字也是会一直重复出现的。那么我们现在就试着使用享元模式来对这些字进行处理。这里暂且使用字母代替:

 

namespace Flyweight_Pattern  {      class FlyweightPattern      {      }        #region 抽象享元角色——抽象公共接口      public abstract class Flyweight      {          /// <summary>          /// 表述输出的位置          /// </summary>          /// <param name="externalstate">外在的状态,随着环境改变而改变</param>          public abstract void OutInput(int externalstate);      }      #endregion        #region 具体享元角色——实现其具体      public class SpecificFlyweight : Flyweight      {          private string Innerstate;          /// <summary>          /// 内部状态接收          /// </summary>          /// <param name="innerstate">内部状态</param>          public SpecificFlyweight(string innerstate)          {              this.Innerstate = innerstate;          }          /// <summary>          /// 实现方法          /// </summary>          /// <param name="externalstate">外部状态</param>          public override void OutInput(int externalstate)          {              Console.WriteLine($"内部状态:{Innerstate}————外部状态:{externalstate}");          }      }      #endregion        #region 享元工厂角色——对享元角色进行创建及管理的      public class FlyweightFactory      {          public Dictionary<string, SpecificFlyweight> keyValuePairs = new Dictionary<string, SpecificFlyweight>();            public SpecificFlyweight GetFlyweight(string Key)          {              SpecificFlyweight specific = null;              if (!keyValuePairs.ContainsKey("A"))              {                  Console.WriteLine("暂时未遇见该Key");                  keyValuePairs.Add(Key, new SpecificFlyweight(Key));                  Console.WriteLine("现已保存该Key");              }              else              {                   specific = keyValuePairs[Key] as SpecificFlyweight;              }              return specific;          }      }      #endregion  }

 

namespace Flyweight_Pattern  {      class Program      {          static void Main(string[] args)          {                ///初始化享元工厂              FlyweightFactory factory = new FlyweightFactory();              while (true)              {                  Console.WriteLine("请输入字符的位置:");                  var indexstring = Console.ReadLine();                  if (int.TryParse(indexstring, out int index))                  {                      Console.WriteLine("请输入字符:");                      string str = Console.ReadLine();                        ///判断字符是否创建                      Flyweight flyweight = factory.GetFlyweight(str);                        //如果存在则输出信息                      if (flyweight != null)                      {                          flyweight.OutInput(index);                      }                  }                  else                  {                      Console.WriteLine("请输入数字!");                  }                  Console.WriteLine("结束请输入N");                  if (Console.ReadLine()=="N")                  {                      break;                  }              }                Console.WriteLine("已结束");              Console.ReadLine();          }      }  }

 

使用场景及优缺点

  这里我们需要注意的是划分好外部状态和内部状态,否则混淆之后可能引起线程安全问题。同时必不可少的是一个工厂对象进行控制。

这里我们解释下在享元模式中的内在状态和外在状态:

内在状态:不随环境的变化而变化,上面例子中不管位置如何变化,A就是A。字母A就是内在状态。

外在状态:会随着环境的变化而变化,上面例子中位置变化所以输出的位置也是不一致的。字母A的位置就是外在状态

一、 使用场景

  对于享元模式来说其使用场景可分以下四点:

1、系统需要大量相似对象

2、创建这些对象需要花费大量资源

3、状态可分为内在状态和外在状态,可以根据内在状态分为各种组。

4、需要缓冲池的场景

二、 优点

1、享元模式的优点最主要的是极大的减少了系统中对象的创建,降低了内存使用提高了效率

三、 缺点

任何东西来说都是有利有弊的,我们看下享元模式又存在哪些弊端呢

1、为了使一些对象共享,对对象区分了内在状态和外在状态,使系统逻辑变为复杂了,使系统更加难于理解了。

扩展

  我们再看看享元模式和之前我们有提到过的原型模式和单例模式的一些区别吧。我们首先回顾下前面所说的原型模式和单例模式:
    原型模式:使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。

    单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    享元模式:运用共享技术有效地支持大量细粒度的对象。

  原型模式——享元模式

    我们再来分析,原型模式是通过原型实例指定创建对象。达到类的复用。通过深浅拷贝类来实现复用以节约资源开销。再看享元模式抽出相似的一些对象,定义其内在状态和外在状态来达到对象共享。运用共享的技术来支持大量细粒度的对象。也就是前面更倾向于实现复用,而享元模式更加倾向于共享使用。所以也不适合放一起进行比较的。

  单例模式——享元模式

    我们再看单例模式,单例模式保证的是类仅有一个实例,然后提供一个全局访问点,所有访问都可以访问得到。达到的是一个实例数据的共享。我们再看享元模式,享元模式通过的是将相似的对象进行共享,然后控制内在状态和外在状态来达到变化。享元模式是对象级别的,也就是实现的是多个对象——对象池。而单例模式是一类一个对象的实例。享元模式更加注重的是节约内存空间,提高其性能。然而单例模式注重的是数据的共享。

总结

  今天的享元模式就暂时介绍的这么多,享元模式精髓也就是达到对象的共享,达到共享的话就需要抽出一部分东西达到相似。所以这又区分了内在状态和外在状态。内在状态不随环境变化而变化,属于可共享的。而外在状态随着环境改变而改变,所以不能共享的。在.Net开发中,我们经常使用到的额String类型的实现就采用了享元模式的设计。在string中具有相同字符序列的String对象不会重复创建。具体细节想要研究的可以自行查询资料。在我们使用设计模式的时候有一些点还是需要多次强调及注意的。基于原则设计。视情况采用设计模式。不要为了使用设计模式而去使用设计模式。一切都是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

  勇敢的面对不一定成功,但你不面对就一定不成功。

    C#设计模式系列目录

欢迎大家扫描下方二维码,和我一起踏上设计模式的闯关之路吧!