代码重构(一)之从代码设计层面着手实践:App.Config的读写封装

前缀

我们在使用某些软件的时候,会看到有参数配置的一些功能,实现它的方式有很多种。最主要的是我们选择存储的“介质”类型不同而导致最后代码的实现不同。这些介质一般有数据库和文件两种存储方式,而文件的种类就更多了,比如txt、ini、config各种扩展名的不同,.NET framework也有其对应的实现方式,至于具体如何针对实际的场景来选择合适的存储方式,本文略过。

 

主题

这里我们主要针对config类型文件的appSettings段进行读写,很多文章只讲实现,但是我们在实际的项目中时才能体会到如何合理的进行读写设计与实现。问题虽小,但要做到无隐患,还是得从基础“玩”起。

 

网上相应的实现

http://developer.51cto.com/art/200908/146303.htm(转),该实现有设计上的缺陷,在修改appsetting的时候,可以不用针对原有的键进行删除,然后再新增。想不出作者,为何不直接赋值后save来达到编辑键值的目的呢?

 

代码初期实现

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;

namespace Fundation.Core
{
    public class Config
    {
        public static string Get(string name)
        {
            return System.Configuration.ConfigurationManager.AppSettings[name];
        }

        public static string GetConnectString(string name)
        {
            return ConfigurationManager.ConnectionStrings[name].ConnectionString;
        }
        /// <summary>
        /// 获取config键值
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <param name="defaultValue">默认值</param>
        /// <returns></returns>
        public static string Get(string keyName, string defaultValue)
        {
            #region
            string value = "";
            if (System.Configuration.ConfigurationManager.AppSettings[keyName] == null)
            {
                Save(keyName, defaultValue);
                value = defaultValue.ToString();
            }
            else
                value = System.Configuration.ConfigurationManager.AppSettings[keyName];
            return value;
            #endregion
        }
        /// <summary>
        /// 保存App.config
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <param name="value">目标值</param>
        public static string Save(string keyName, string value)
        {
            #region
            Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            if (System.Configuration.ConfigurationManager.AppSettings[keyName] != null)
                configuration.AppSettings.Settings[keyName].Value = value;
            else
                configuration.AppSettings.Settings.Add(keyName, value);
            configuration.Save();
            return value;
            #endregion
        }
        /// <summary>
        /// 更新App.config文件
        /// </summary>
        /// <param name="keyName"></param>
        /// <param name="value"></param>
        public static string Update(string keyName, string value)
        {
            #region
            Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            configuration.AppSettings.Settings[keyName].Value = value;
            configuration.Save();
            return value;
            #endregion
        }
    }
}
View Code

 

针对上述代码的重新设计

由于标题中我标明了“封装”二字,所以我们在代码设计时得考虑到它封装后的稳健性,这里主要指明几点封装时注意点儿:

  1. 从接口方法设计上,config文件无非就提供读、写两个操作,所以公开出来的接口方法不应有多余的方法public出来。
  2. 在读取时,如果未发现目标键,则会创建该键,并将默认值赋予该键(这是之前的设计,好处是我们不需要手工去维护默认的appSettings段参数,让默认参数用代码自动生成)。
  3. 在特定场景下重复读会出现bug。因为如果写入新值后,再利用以下代码读取时还是会获取到程序启动时首次读取的值。

其次需要新增以下设计要点:

  1. 解决并发读写,主要是针对线程安全的场景,两个以上的线程在同时操作文件时,会throw Exception。
  2. 读写效率上的改进,主要针对在参数变更后才进行文件写保存。

 

改进后的代码实现

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;

namespace Fundation.Core
{
    public class Config
    {
        private static Hashtable _configCacheGroup
            = new Hashtable();
        /// <summary>
        /// 并发写操作的“锁”
        /// </summary>
        private static readonly object _lockConfig = true;

        private static Configuration _configuration = ConfigurationManager
            .OpenExeConfiguration(ConfigurationUserLevel.None);
        /// <summary>
        /// 获取config中AppSettings节对应的节点值
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <returns></returns>
        private static string get(object keyName)
        {
            return ConfigurationManager.AppSettings[keyName.ToString()];
        }

        /// <summary>
        /// 获取config中的连接字符串
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <returns></returns>
        public static string GetConnectString(string keyName)
        {
            return ConfigurationManager
                .ConnectionStrings[keyName]
                .ConnectionString;
        }
        /// <summary>
        /// 获取config键值
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <param name="defaultValue">默认值(引用)</param>
        public static void Get(object keyName
            , ref object defaultValue)
        {
            #region
            string value = get(keyName);

            if (!_configCacheGroup.Contains(keyName))
            {
                if (value == null)
                    Save(keyName.ToString(), defaultValue.ToString());
                _configCacheGroup.Add(keyName, defaultValue);
            }
            else
                defaultValue = _configCacheGroup[keyName];
            #endregion
        }
        /// <summary>
        /// 保存App.config文件对应键值
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <param name="value">保存值</param>
        private static string Save(string keyName
            , string value)
        {
            #region
            _configuration.AppSettings.Settings.Add(keyName, value);

            lock (_lockConfig)
                _configuration.Save();

            return value;
            #endregion
        }
        /// <summary>
        /// 更新App.config文件对应键值
        /// </summary>
        /// <param name="keyName">键名</param>
        /// <param name="value">更新值</param>
        public static void Update(object keyName, ref object newValue)
        {
            #region
            if (_configCacheGroup.Contains(keyName))
            {
                if (_configCacheGroup[keyName] != newValue)
                {
                    //此处给对应的键进行赋值,下面的save操作会应用此更新。
                    _configuration.AppSettings.Settings[keyName.ToString()].Value = newValue.ToString();

                    lock (_lockConfig)
                    {
                        _configuration.Save();
                        _configCacheGroup[keyName] = newValue;
                    }
                }
            }
            else
                ExtConsole.Write(string.Format("系统应针对{0}键先进行读取后才可保存", keyName));
            
            #endregion
        }
    }
}
View Code

 

代码改进的几点说明

  1. 其中新增了config的缓存哈希表来存储所有键值对,为了防止重复写操作,解决了上面设计要点的第三点。
  2. 代码中加lock的地方,保证了并发读写的线程安全。
  3. 把一些不必要的接口进行关闭,读者可对照改进前后的方法权限来自行体会。
  4. 将参数类型和返回值进行了调整,这里修改的比较仓促,感觉修改后比原来的装拆箱操作要少很多。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。