Flyweight
享元模式:运用共享技术有效的支持大量细粒度的对象
对应的UML图如下:
1018770-20180521224951802-1630441597.png

  • FlyWeight:抽象享元角色,是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • ConcreteFlyweight:具体享元角色
  • FlyweightFactory:享元工厂角色。对享元对象的创建与管理
  • UnsharedConcreateFlyweight:非享元角色,为外部状态信息

在享元模式中。有两个很重要的概念:外部状态、内部状态

  • 外部状态:一个对象的可变部分,不可共享
  • 内部状态:一个对象的不变的部分,共享的就是内部状态

我们在对一个对象的创建使用享元模式的时候首先就得考虑对象的外部状态、内部状态,将不变的部分抽象分离出来作为共享的部分。下面以具体的例子说明。
享元模式的目的说简单点就是将对象缓存,减少对象的创建,从而降低系统的开销,降低系统的压力
从这一般我们能想到数据库的池化技术。我们当获取一个数据库连接对象,当没有的时候会直接创建返回,使用完成后会放进数据库连接池,当下次又来获取连接对象的时候,如果存在“空闲”的连接对象,就会直接从连接池当中获取返回,这样就减少了连接对象的创建,降低了内存开销。
简单的来说(不考虑具体实现),每个数据库连接都需要一个数据库连接字符串,其包括了地址、账户等信息,对于不同的数据库实例这些都是不同的,那么这些参数我们完全可以看成外部状态,我们数据库对象在排除外部状态后本身也就是内部状态了。
在某些情况之下,对于一些简单对象来说,可能根本不存在外部状态一说,所以上面UML中的UnsharedConcreateFlyweight 也可能随对象的不同,可能不会存在。

1、场景描述

根据上面所说的数据库连接池例子, 用代码实现一个简单的数据库连接池管理程序。

2、实现

  1. /// <summary>
  2. /// 抽象享元(FlyWeight)
  3. /// 数据库连接抽象
  4. /// </summary>
  5. public interface IConnection
  6. {
  7. /// <summary>
  8. /// 打印连接字符串
  9. /// </summary>
  10. void PrintConnectionStr();
  11. }
  12. /// <summary>
  13. /// 具体数据库连接对象
  14. /// </summary>
  15. public class Connection : IConnection
  16. {
  17. // 连接字符串
  18. private readonly string _connectionStr;
  19. public Connection(string connectionStr)
  20. {
  21. this._connectionStr = connectionStr;
  22. Console.WriteLine("开始创建连接");
  23. }
  24. /// <summary>
  25. /// 打印连接字符串
  26. /// </summary>
  27. public void PrintConnectionStr()
  28. {
  29. Console.WriteLine($"The connection string is: {_connectionStr}");
  30. }
  31. }
  32. /// <summary>
  33. /// 数据库连接对象工厂
  34. /// </summary>
  35. public class ConnectionFactory
  36. {
  37. private static ConcurrentDictionary<string, IConnection> dics = new ConcurrentDictionary<string, IConnection>();
  38. private static readonly object _lock = new object();
  39. /// <summary>
  40. /// 获取连接对象
  41. /// </summary>
  42. /// <returns></returns>
  43. public static IConnection GetConnection(string connectionStr)
  44. {
  45. if (string.IsNullOrWhiteSpace(connectionStr))
  46. throw new ArgumentNullException("连接字符串为空");
  47. connectionStr = connectionStr.Trim();
  48. if (!dics.ContainsKey(connectionStr))
  49. {
  50. lock (_lock)
  51. {
  52. if (!dics.ContainsKey(connectionStr))
  53. {
  54. IConnection connection = new Connection(connectionStr);
  55. dics.TryAdd(connectionStr, connection);
  56. }
  57. }
  58. }
  59. return dics[connectionStr];
  60. }
  61. }
  62. // 客户端调用
  63. string connectionStr = "server=10.21.1.36;user id=sa;password=#123;persistsecurityinfo=True;database=test";
  64. IConnection connection1 = ConnectionFactory.GetConnection(connectionStr);
  65. connection1.PrintConnectionStr();
  66. Console.WriteLine("***************************");
  67. IConnection connection2 = ConnectionFactory.GetConnection(connectionStr);
  68. connection2.PrintConnectionStr();
  69. Console.WriteLine("***************************");
  70. IConnection connection3 = ConnectionFactory.GetConnection(connectionStr);
  71. connection3.PrintConnectionStr();
  72. string connectionStr1 = "server=10.45.11.36;user id=sa;password=12;persistsecurityinfo=True;database=test";
  73. IConnection connection4 = ConnectionFactory.GetConnection(connectionStr1);
  74. connection1.PrintConnectionStr();
  75. Console.WriteLine("***************************");
  76. IConnection connection5 = ConnectionFactory.GetConnection(connectionStr1);
  77. connection2.PrintConnectionStr();
  78. Console.WriteLine("***************************");
  79. IConnection connection6 = ConnectionFactory.GetConnection(connectionStr1);
  80. connection3.PrintConnectionStr();

运行结果如下:
image.png
可以看见相同的连接字符串在多次获取连接对象的时候只会直接取出返回。并不会每次都去重新创建生成,降低了开销。

3、优缺点

  • 优点:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力
  • 缺点:需要将对象剥离出外部状态、内部状态,且分开进行管理,在一定的程度上提高了编码难度、程序复杂性。

    4、应用场景

    当我们需要创建大量的相同或者相似的对象实例的时候,就可以考虑使用享元模式。 比如字符串往往就会对象一个常量池来管理大量的字符串,达到享元的目的。