[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();
    }
}
posted @   Clark159  阅读(1261)  评论(2编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示