[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上。
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。
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模式』的流程做封装。
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模式』的功能。
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的数量(两个)来执行。
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模式』的功能。
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);
}
}
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();
}
}
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。








浙公网安备 33010602011771号