UML软件工程组织

灵活的.NET体系结构之简化配置服务(2)
作者:宁凯编译 本文选自:赛迪网 2002年12月27日

 

更改配置文件是比较容易的一件事情,这样就可以修改电子邮件的具体外表形式。请注意,如何使用string.Format(...) 将动态值替换到从配置文件中读取出来的模板中。另请注意,如何使用CDATA 部分将HTML嵌入到 XML 文件之间。


将公共静态服务转变成接口


在讲述如何实现这些方法之前,我们先来解释如何将一个静态方法转变成为一个接口。具体理由如下:AppServices 是一个接口集,其中的每个接口均表示一种服务。例如:

public AppServices
{
public static IConfig getConfigurationService();
pulbic static IFacgtory getFactoryService();
public static ILog getLoggingService();
...any other application level services
}


我们来看看 IConfig可能包含的内容:

public interface IConfig
{
public static getValue(string key);
// throws an exception if the key is not found or has an
// empty string
public static getValueHonourWhiteSpace(string key)
// throws an exception if the key is not found
public static getValue(string key, string default);
// returns the default if the key is not found or has an
// empty string
public static getValueHonourWhiteSpace(string key, string default)
// returns the default if the key is not found
}


我们可能拥有另一支持XPath 的接口,如下所示:

public interface IConfigXPath
{
//Xpath support
public static getXPathValue(string key);
// throws an exception if the key is not found or has an
// empty string
public static getXPathValueHonourWhiteSpace(string key)
// throws an exception if the key is not found
public static getXPathValue(string key, string default);
// returns the default if the key is not found or has an
// empty string
public static getXPathValueHonourWhiteSpace(string key, string default)
// returns the default if the key is not found
}


现在,我们继续操作,将会得到一个如下所示的实现:

public class DefaultConfig : IConfig, IXPathConfig
{
... Implements all the methods
}


现在对 AppServices 进行编码,如下所示:

public class AppServices
{
private IConfig m_config = new DefaultConfig();
// Potentially one can get this from a factory
public static getIConfig() { return m_config; }
... and others
}


为什么要将静态方法转变成接口呢?这是因为对于一些情况,区分大小写是很有帮助的,但是对于另外一些情况,就没有必要区分大小写。而接口则可以很好地支持这两种情况:

public class CaseSensitiveConfig : IConfig
{
//... implement your keys with case sensitive
}
public class CaseInsensitiveConfig : IConfig
{
//... implement your keys with case insensitive
}


应用程序服务在运行时将根据实际情况返回一个适当的实现。如果您想知道如何实现,可以参考工厂服务。或者您可以阅读一些参考文献。下面我们演示一个更完整的示例:

public class CaseInsensitiveMultiFileConfig : IConfig
{
// Implement your keys with case insensitivity and
// read from multiple config files
}


我们一直在强调配置文件的重要性,用不了多久,这种想法就会得到广泛应用。XML 配置文件将会随处可见。在一个团队中,可能需要将配置信息拆分成多个文件,以便增强团队成员之间的合作。

到目前为止,我们已经讨论了配置服务的优点以及如何使用配置服务的内容。这些内容应该说是经验的积累。接下来我们来看一些具体的实现。


实现


方法是足够简单了。所有的配置均可以在一个section中设置。诸如 DefaultConfig 这样的类可以读取此section中的数据内容。一旦获取了所需的数据内容,DefaultConfig 类就可以遍历XML文档中的每一个结点,并在数据字典或哈希表中为每一个关键字和值设置对应的一项。这里所提到的数据字典将可以响应客户端发送的关键字请求。

Section 处理器代码如下:

public class SimpleConfigurationSectionHandler :IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
return section;
} // end of method
} // end of class


DefaultConfig代码:

public class DefaultConfig : IConfig {
// keep a dictionary of values
private IDictionary m_keyValuePairs
// implement methods of IConfig using the above
// dictionary details left to you
// Constructor, where it reads your SimpleConfiguration
// XML node using the section handler above
public DefaultConfig()
{
// read the xml section for general config
// Section name: SimpleConfiguration
XmlNode xmlNode =
(XmlNode)System
.Configuration
.ConfigurationSettings
.GetConfig("SimpleConfiguration");
if(xmlNode != null)
{
m_keyValuePairs = createDictionary(m_genConfigXmlNode);
}
}
}
// Here is the createdictionary
private IDictionary createDictionary(XmlNode genConfigXmlNode)
{
IDictionary ht = new Hashtable();
if(genConfigXmlNode != null &&
genConfigXmlNode.ChildNodes != null &&
genConfigXmlNode.ChildNodes.Count > 0)
{
// walk through each node
// if it is a leaf node add it to the hash table
// if it is not continue the process
walkTheNode(ht,"",genConfigXmlNode);
}
return ht;
}
// Here is how you walk the nodes recursively
// to get your keys
private void walkTheNode(IDictionary ht, string parent, XmlNode node)
{
if(node != null)
{
if (node.NodeType == XmlNodeType.Comment)
{
return;
}
if (node.HasChildNodes == false)
{
if (node.NodeType == XmlNodeType.Text)
{
// no children
string leaf = node.Value;
ht.Add(parent.ToLower(),leaf);
// end of the recursive call
return;
}
else if (node.NodeType == XmlNodeType.CDATA)
{
XmlCDataSection cdataSection = (
System.Xml.XmlCDataSection)node;
string leaf = cdataSection.Data;
ht.Add(parent.ToLower(),leaf);
// end of the recursive call
return;
}
else
{
string key = parent + "/" + node.Name;
string val = "";
ht.Add(key.ToLower(), val);
return;
}
}
else
{
string newparent = parent + "/" + node.Name;
// has children
// walk all the children
for(int i=0;i<node.ChildNodes.Count;i++)
{
// recursive call
walkTheNode(ht,newparent,node.ChildNodes[i]);
}
}
}
}


提示:编码时请特别关注如何处理 CDATA 和空文本结点。

在下一篇文章中,我们将在这种简单的配置服务基础之上对其进行扩展构成工厂服务FactoryService。工厂服务可以使您的体系结构变得更加灵活。

(责任编辑 Sunny)





版权所有:UML软件工程组织