通用XML读写和配置(一)

在软件开发过程中,经常使用到XML文件作为配置文件,保存一些配置信息。 为了方便对程序配置文件的读写,微软特别为.Net程序提供了程序配置文件,如web.config,App.config,这些配置文件通常会自动生成在程序启动目录下,并具有特定的格式。同时,.Net Framework还提供了一组读写配置文件的接口,这些接口被包含在命名空间System.Configuration中。 但由于接口固定,系统自带的配置文件格式不容易扩展,这些配置文件通常只能用来保存一些简单的信息,比如数据库连接信息或应用程序标识。
那如何保存复杂的配置信息呢?比如数据持久化、系统界面布局,或者是程序运行过程中产生的临时数据等,这些信息通常都需要自己定义对应的类和结构,多个类之间还存在组合、连接等关系。
一般的方法是:实现一个解析类,用来存取要保存的数据。在实现读写方法时,需要逐一的解析XML文件的节点,并在解析每个节点过程中找到对应的对象,分别赋值。此时,解析类相当于一个控制器(Controller),负责与所有数据对象(类)的交互,并组织它们的存储结构。示意图如下:
 通用XML读写和配置(一)_第1张图片
由于解析类和数据对象类之间存在紧耦合,存在以下不足:
1.代码无法复用。如果有新的数据对象需要配置,还得重新实现一个解析类。
2.解析类的维护成本加大。每个数据对象类的变化和更改,都需要修改解析类中的相应部分,从而增加维护成本。
3.不利于数据对象扩展。如果有新的数据也需要通过配置保存,则需要修改解析类,并调整和组织所有数据对象的存储结构。
  
基于此,不断尝试将解析类和数据对象类之间解耦,以降低代码维护成本,并希望最大程度的复用代码。通过多次的项目实践,发现如下思路:
1.各个数据对象负责解析自己所对应的XML节点,实现对该节点的写入和读取(序列化和反序列化)。
2.对象只负责解析自己,如果有子对象,则要求子对象具备自我解析接口,供上级对象调用。
3.最终的与XML文件的接口由最大一级数据对象来实现,外部通过该接口来实现XML配置文件的读写。
  
下面以一个小示例来说明,这个示例中假设要使用XML文件来配置数据库信息,数据库信息共有三类数据:数据库,数据表,列,其中,每个数据库有名称和创建日期等信息,包含0或多个数据表;数据表有名表和描述信息,由1至多个列构成;列的信息由列名、数据类型、是否为空等常用属性构成。这个示例可能没有多少实际意义(除非是用于数据持久化,但通常是不用我们自己实现了:)),只用于说明当多种数据之间具有组合关系时,如何实现较灵活的XML读写功能。类图如下:
 通用XML读写和配置(一)_第2张图片
在示例中,首先定义一个读写XML的接口,所有需要XML文件中存取配置的数据对象必须实现此接口。该接口的定义如下:
代码
    
    
/// <summary>
/// XML保存和设置接口
/// </summary>
public interface IXmlStorage
{
/// <summary>
/// 根据对应的XML节点来获取各属性值
/// </summary>
/// <param name="factoTypeNode"> 读取信息的节点 </param>
void GetValueFromXml(System.Xml.XmlNode objectNode);
/// <summary>
/// 将对象本身格式化为XML
/// </summary>
/// <param name="xmldoc"> 需要写入的文档 </param>
/// <param name="parentEle"> 生成节点的父元素节点 </param>
void SetValueToXml(System.Xml.XmlDocument xmldoc, XmlElement parentEle);
}
其它的数据类定义如下:
代码
     
     
/// <summary>
/// 数据库对象
/// </summary>
public class DataBase : IXmlStorage
{
/// <summary>
/// 数据库名称
/// </summary>
public string Name = string .Empty;
/// <summary>
/// 描述
/// </summary>
public string Description = string .Empty;
/// <summary>
/// 创建日期
/// </summary>
public DateTime CreateDate = DateTime.Now;
/// <summary>
/// 数据表集合
/// </summary>
public List < Table > TableList = new List < Table > ();

/// <summary>
/// 配置文件实例
/// </summary>
XmlDocument xmldoc = new XmlDocument();

/// <summary>
/// 默认配置文件名称
/// </summary>
public static readonly string DefaultConfigXml = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + " DatabaseConfig.xml " ;

#region IXmlStorage Members

public void GetValueFromXml(XmlNode objectNode)
{
if (objectNode == null )
return ;
if (objectNode.Name.Equals( " database " , StringComparison.CurrentCultureIgnoreCase))
{
XmlAttributeCollection attCol
= objectNode.Attributes;
// 从节点属性中获取值
for ( int i = 0 ; i < attCol.Count; i ++ )
{
XmlAttribute att
= attCol[i];
if (att.Name.Equals( " name " , StringComparison.CurrentCultureIgnoreCase))
this .Name = att.Value;
if (att.Name.Equals( " description " , StringComparison.CurrentCultureIgnoreCase))
this .Description = att.Value;
if (att.Name.Equals( " date " , StringComparison.CurrentCultureIgnoreCase))
{
try
{
this .CreateDate = DateTime.Parse(att.Value);
}
catch
{
this .CreateDate = DateTime.Now;
}
}
}
// 先清空原来的下级节点内容(因为有可能多次重复加载)
this .TableList.Clear();
// 获取下一级子节点的值
XmlNodeList nodeList = objectNode.ChildNodes;
if (nodeList != null && nodeList.Count > 0 )
{
for ( int j = 0 ; j < nodeList.Count; j ++ )
{
XmlNode node
= nodeList[j];
if (node.Name.Equals( " table " , StringComparison.CurrentCultureIgnoreCase))
{
Table t
= new Table();
t.GetValueFromXml(node);
this .TableList.Add(t);
}
}
}
}
}

public void SetValueToXml(XmlDocument xmldoc, XmlElement parentEle)
{
XmlElement dbEle
= xmldoc.CreateElement( " Database " );
dbEle.SetAttribute(
" name " , this .Name);
dbEle.SetAttribute(
" description " , this .Description);
dbEle.SetAttribute(
" date " , this .CreateDate.ToString());
xmldoc.AppendChild(dbEle);
// 由于此时是根节点,故parentEle为空
// 添加下一级的子节点
for ( int i = 0 ; i < this .TableList.Count; i ++ )
{
Table t
= this .TableList[i];
t.SetValueToXml(xmldoc, dbEle);
}
}
#endregion
}

/// <summary>
/// 数据表
/// </summary>
public class Table : IXmlStorage
{
/// <summary>
/// 表名
/// </summary>
public string Name = string .Empty;
/// <summary>
/// 描述
/// </summary>
public string Description = string .Empty;
/// <summary>
/// 列集合
/// </summary>
public List < Column > ColumnList = new List < Column > ();

#region IXmlStorage Members

public void GetValueFromXml(System.Xml.XmlNode objectNode)
{
if (objectNode == null )
return ;
if (objectNode.Name.Equals( " table " , StringComparison.CurrentCultureIgnoreCase))
{
XmlAttributeCollection attCol
= objectNode.Attributes;
// 从节点属性中获取值
for ( int i = 0 ; i < attCol.Count; i ++ )
{
XmlAttribute att
= attCol[i];
if (att.Name.Equals( " name " , StringComparison.CurrentCultureIgnoreCase))
this .Name = att.Value;
if (att.Name.Equals( " description " , StringComparison.CurrentCultureIgnoreCase))
this .Description = att.Value;
}
// 先清空原来的下级节点内容(因为有可能多次重复加载)
this .ColumnList.Clear();
// 获取下一级子节点的值
XmlNodeList nodeList = objectNode.ChildNodes;
if (nodeList != null && nodeList.Count > 0 )
{
for ( int j = 0 ; j < nodeList.Count; j ++ )
{
XmlNode node
= nodeList[j];
if (node.Name.Equals( " column " , StringComparison.CurrentCultureIgnoreCase))
{
Column c
= new Column();
c.GetValueFromXml(node);
this .ColumnList.Add(c);
}
}
}
}
}

public void SetValueToXml(System.Xml.XmlDocument xmldoc, System.Xml.XmlElement parentEle)
{
XmlElement tblEle
= xmldoc.CreateElement( " Table " );
tblEle.SetAttribute(
" name " , this .Name);
tblEle.SetAttribute(
" description " , this .Description);
parentEle.AppendChild(tblEle);
// 由于此时是根节点,故parentEle为空
// 添加下一级的子节点
for ( int i = 0 ; i < this .ColumnList.Count; i ++ )
{
Column c
= this .ColumnList[i];
c.SetValueToXml(xmldoc, tblEle);
}
}

#endregion
}

 

其中,作为所有数据对象中的最大(级别)的对象,DataBase除了实现XML节点读写接口外,还需要提供加载和写入XML文件的方法,供外部代码调用,这也是读写XML文件的唯一入口。实现代码如下:
代码
      
      
#region 读取和保存XML文件
public bool LoadFromFile( string xmlPath)
{
try
{
this .xmldoc.Load(xmlPath);
XmlNode rootNode
= this .xmldoc.SelectSingleNode( " //Database " );
if (rootNode != null )
{
this .GetValueFromXml(rootNode);
}
}
catch (Exception ex)
{
string msg = " 加载配置文件失败,发生异常: " + ex.Message;
throw new Exception(msg);
}
return true ;
}

public bool SaveToFile( string xmlPath)
{
try
{
this .xmldoc = new XmlDocument(); // 重新构造一个XML文档
XmlDeclaration xDeclare = this .xmldoc.CreateXmlDeclaration( " 1.0 " , " GB2312 " , null );
this .xmldoc.AppendChild(xDeclare);
this .SetValueToXml( this .xmldoc, null );
this .xmldoc.Save(xmlPath);
return true ;
}
catch (XmlException ex)
{
throw new Exception( " 保存配置文件失败,发生异常: " + ex.Message);
}
}

#endregion

 

完成了所有数据对象的定义,我们就可以通过XML文件来配置和存储这些数据。
示例代码如下:

 

 

代码
       
       
DataBase db = new DataBase();
db.Name
= " 数据库1 " ;
db.Description
= " for test " ;
Table tbl
= new Table();
tbl.Name
= " Student " ;
tbl.Description
= " 学生信息表 " ;
db.TableList.Add(tbl);
Column c1
= new Column();
c1.Name
= " StudentName " ;
c1.ColumnType
= 1 ;
c1.IsNullable
= false ;
c1.Remark
= " 姓名 " ;
Column c2
= new Column();
c2.Name
= " Number " ;
c2.ColumnType
= 1 ;
c2.IsPrimaryKey
= true ;
c2.Remark
= " 学号 " ;
Column c3
= new Column();
c3.Name
= " Age " ;
c3.ColumnType
= 2 ;
c3.Remark
= " 年龄 " ;

tbl.ColumnList.Add(c1);
tbl.ColumnList.Add(c2);
tbl.ColumnList.Add(c3);

// 保存数据到XML文件
db.SaveToFile( " D:\\mytest.xml " );

// 从XML文件中加载数据
DataBase db2 = new DataBase();
db2.LoadFromFile(
" D:\\mytest.xml " );

 

总结:
1.在数据对象的定义过程中,我们除了要定义数据本身的属性和方法,还要定义对象本身所对应的XML节点结构,从而使对象能够自我解析XML节点,达到了解耦的目的。
2.数据对象负责解析自己,这算是“职责单一”的设计原则的体现吧?哈哈,后来看了设计模式,这似乎就是传说中的装饰者模式呢。
3.虽然这已经改进了XML文件的存取配置,但依然不算很通用,能不能实现一种更通用甚至是万能的XML配置方法呢?请看下回分解。
 
示例代码:下载

 

你可能感兴趣的:(xml)