• [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();
        }
    }
    
  • 相关阅读:
    学习进度三
    开课博客之个人介绍
    个人作业--数组
    学习进度二
    开学第一次测试
    实现点击不同的按钮加载不同的css
    Web存储
    HTML5(常用的表单控件)
    JS(获得当前时间并且用2015-01-01格式表示)
    JS(event事件)
  • 原文地址:https://www.cnblogs.com/clark159/p/2344779.html
Copyright © 2020-2023  润新知