[Architecture Pattern] Singleton Pool
动机 :
在开发与数据库沟通的系统时,因为建立数据库联机是比较昂贵的。
所以ADO.NET在背后帮开发人员,实做了 ConnectionPool的机制。
将系统内建立的数据库联机做快取,当系统要使用时就直接使用快取联机,避免了每次都建立新数据库联机的花费。
并且实际上在使用ADO.NET时,开发人员对于背后的ConnectionPool机制其实是无感的。
要让开发人员无感,可是又能完成快取的功能,这真的要花一点工夫去设计。
本文介绍一个『Singleton Pool模式』。
定义对象之间的职责跟互动,用来建置类似ConnectionPool功能的对象池功能,并且提供开发人员无感的使用界面。
为自己做个纪录,也希望能帮助到有需要的开发人员。
结构 :
『Singleton Pool模式』是Flyweight Pattern的延伸,有兴趣的开发人员可以找相关文章做参考。
模式的结构如下:
主要的参与者有:
NativeConnection
-实际提供功能的对象。
ReferenceRecord
-快取NativeConnection,并且纪录有多少客户端正在使用中。
ConnectionPool
-建立ReferenceRecord用来快取对象及记录用户。
-当有人要使用NativeConnection,可是系统内没有快取的时候,建构快取。
-当有人要使用NativeConnection,可是系统内已有快取的时候,回传快取。
-当没有人使用NativeConnection,可是系统内已有快取的时候,解构快取。
Connection
-合成NativeConnection功能提供外部使用。
-将NativeConnection的建构、解构,交由ConnectionPool去处理。
透过下面的图片说明,可以了解相关对象之间的互动流程。
实做 :
范列下载 :
范例的程序代码较多,实做说明请参照范例程序内容。
SingletonPoolSample点此下载
范列实做 :
范例内容实做一个模拟的ConnectionPool,它会快取实际联机到数据库的对象,并且会将内部执行讯息打印到Console上。
透过这个范例,可以清楚的了解如何实做以及执行效果。
首先在项目里实做一个虚拟的NativeConnection,用来仿真实际联机到数据库的功能以及将执行讯息打印到Console上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class NativeConnection : IDisposable { // Fields private readonly string _connectionString = null ; // Constructor public NativeConnection( string connectionString) { #region Require if ( string .IsNullOrEmpty(connectionString) == true ) throw new ArgumentNullException(); #endregion _connectionString = connectionString; Console.WriteLine( string .Format( "Connect to database.[{0}]" , _connectionString)); } public void Dispose() { Console.WriteLine( string .Format( "Disconnection to database.[{0}]" , _connectionString)); } // Methods public void Query() { Console.WriteLine( string .Format( "Query.[{0}]" , _connectionString)); } } |
再来建立ReferenceRecord,用来快取NativeConnection,并且纪录谁使用了NativeConnection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | internal sealed class ReferenceRecord<TReferenceKey, TReferenceItem> { // Fields private readonly List<Guid> _consumerIdList = new List<Guid>(); // Constructor public ReferenceRecord(TReferenceKey referenceKey, TReferenceItem referenceItem) { #region Require if (referenceKey == null ) throw new ArgumentNullException(); if (referenceItem == null ) throw new ArgumentNullException(); #endregion this .ReferenceKey = referenceKey; this .ReferenceItem = referenceItem; } // Properties public TReferenceKey ReferenceKey { get ; private set ; } public TReferenceItem ReferenceItem { get ; private set ; } // Methods public void Register(Guid consumerId) { #region Require if (consumerId == Guid.Empty) throw new ArgumentNullException(); #endregion if (_consumerIdList.Contains(consumerId) == false ) { _consumerIdList.Add(consumerId); } } public void Unregister(Guid consumerId) { #region Require if (consumerId == Guid.Empty) throw new ArgumentNullException(); #endregion if (_consumerIdList.Contains(consumerId) == true ) { _consumerIdList.Remove(consumerId); } } public bool NoConsumerRegistered() { if (_consumerIdList.Count <= 0) { return true ; } return false ; } } |
接着实做ConnectionPool,用来将整个『Singleton Pool模式』的流程做封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | public abstract class ReferencePool<TReferenceKey, TReferenceItem> { // Fields private readonly List<ReferenceRecord<TReferenceKey, TReferenceItem>> _referenceRecordCollection = new List<ReferenceRecord<TReferenceKey, TReferenceItem>>(); // Methods public virtual TReferenceItem Create(Guid consumerId, TReferenceKey referenceKey) { #region Require if (consumerId == Guid.Empty) throw new ArgumentNullException(); if (referenceKey == null ) throw new ArgumentNullException(); #endregion // Return Existing ReferenceItem foreach (ReferenceRecord<TReferenceKey, TReferenceItem> referenceRecord in _referenceRecordCollection) { if ( this .CompareReferenceKey(referenceKey, referenceRecord.ReferenceKey) == true ) { referenceRecord.Register(consumerId); return referenceRecord.ReferenceItem; } } // Return New ReferenceItem TReferenceItem referenceItem = this .CreateReferenceItem(referenceKey); if (referenceItem == null ) throw new InvalidOperationException( "CreateReferenceItem failed." ); ReferenceRecord<TReferenceKey, TReferenceItem> newReferenceRecord = new ReferenceRecord<TReferenceKey, TReferenceItem>(referenceKey, referenceItem); _referenceRecordCollection.Add(newReferenceRecord); newReferenceRecord.Register(consumerId); return referenceItem; } public virtual void Release(Guid consumerId, TReferenceKey referenceKey) { #region Require if (consumerId == Guid.Empty) throw new ArgumentNullException(); if (referenceKey == null ) throw new ArgumentNullException(); #endregion // Release Existing ReferenceItem ReferenceRecord<TReferenceKey, TReferenceItem> existingReferenceRecord = null ; foreach (ReferenceRecord<TReferenceKey, TReferenceItem> referenceRecord in _referenceRecordCollection) { if ( this .CompareReferenceKey(referenceKey, referenceRecord.ReferenceKey) == true ) { existingReferenceRecord = referenceRecord; break ; } } if (existingReferenceRecord != null ) { existingReferenceRecord.Unregister(consumerId); if (existingReferenceRecord.NoConsumerRegistered() == true ) { _referenceRecordCollection.Remove(existingReferenceRecord); this .ReleaseReferenceItem(existingReferenceRecord.ReferenceItem); } } } protected abstract TReferenceItem CreateReferenceItem(TReferenceKey referenceKey); protected abstract void ReleaseReferenceItem(TReferenceItem referenceItem); protected abstract bool CompareReferenceKey(TReferenceKey referenceKeyA, TReferenceKey referenceKeyB); } public class ConnectionPool : ReferencePool< string , NativeConnection> { // Methods protected override NativeConnection CreateReferenceItem( string connectionString) { #region Require if ( string .IsNullOrEmpty(connectionString) == true ) throw new ArgumentNullException(); #endregion return new NativeConnection(connectionString); } protected override void ReleaseReferenceItem(NativeConnection connection) { #region Require if (connection == null ) throw new ArgumentNullException(); #endregion connection.Dispose(); } protected override bool CompareReferenceKey( string connectionStringA, string connectionStringB) { #region Require if ( string .IsNullOrEmpty(connectionStringA) == true ) throw new ArgumentNullException(); if ( string .IsNullOrEmpty(connectionStringB) == true ) throw new ArgumentNullException(); #endregion return connectionStringA == connectionStringB; } } |
剩下就是Connection了,这个对象合成NativeConnection对象提供外部使用。并且在建构、解构的函式内,调用Singleton的ConnectionPool,来完成『Singleton Pool模式』的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | public class Connection : IDisposable { // Singleton private static ConnectionPool _poolInstance = null ; private static ConnectionPool PoolInstance { get { if (_poolInstance == null ) { _poolInstance = new ConnectionPool(); } return _poolInstance; } } // Fields private readonly Guid _consumerId = Guid.Empty; private readonly string _connectionString = null ; private readonly ConnectionPool _connectionPool = null ; private readonly NativeConnection _nativeConnection = null ; // Constructor public Connection( string connectionString) : this (connectionString, Connection.PoolInstance) { } public Connection( string connectionString, ConnectionPool connectionPool) { #region Require if ( string .IsNullOrEmpty(connectionString) == true ) throw new ArgumentNullException(); if (connectionPool == null ) throw new ArgumentNullException(); #endregion // Arguments _consumerId = Guid.NewGuid(); _connectionString = connectionString; _connectionPool = connectionPool; // Create _nativeConnection = _connectionPool.Create(_consumerId, _connectionString); if (_nativeConnection == null ) throw new InvalidOperationException( "Create NativeConnection failed." ); } public void Dispose() { // Release _connectionPool.Release(_consumerId, _connectionString); } // Methods public void Query() { // Query _nativeConnection.Query(); } } |
最后我们加上测试的程序以及执行的结果。
由程序代码可以看到,虽然建立了三个Connection来使用,
可是因为对象套用『Singleton Pool模式』的缘故,NativeConnection的建构、解构是依照ConnectionString的数量(两个)来执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Program { static void Main( string [] args) { Connection connectionA = new Connection( "XXX Database" ); Connection connectionB = new Connection( "YYY Database" ); Connection connectionC = new Connection( "XXX Database" ); connectionA.Query(); connectionB.Query(); connectionC.Query(); connectionA.Dispose(); connectionB.Dispose(); connectionC.Dispose(); Console.ReadLine(); } } |
后记 :
如果在一些不大适合使用Singleton的系统内,也可以采用下列的模式。
增加一个ConnectionManager,来套用『Singleton Pool模式』的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class ConnectionManager { // Fields private readonly ConnectionPool _connectionPool = new ConnectionPool(); // Methods public Connection Create( string connectionString) { #region Require if ( string .IsNullOrEmpty(connectionString) == true ) throw new ArgumentNullException(); #endregion return new Connection(connectionString, _connectionPool); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Program { static void Main( string [] args) { ConnectionManager connectionManager = new ConnectionManager(); Connection connectionA = connectionManager.Create( "XXX Database" ); Connection connectionB = connectionManager.Create( "YYY Database" ); Connection connectionC = connectionManager.Create( "XXX Database" ); connectionA.Query(); connectionB.Query(); connectionC.Query(); connectionA.Dispose(); connectionB.Dispose(); connectionC.Dispose(); Console.ReadLine(); } } |
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?