zoukankan      html  css  js  c++  java
  • 通用XML读写和配置(一)

    在软件开发过程中,经常使用到XML文件作为配置文件,保存一些配置信息。 为了方便对程序配置文件的读写,微软特别为.Net程序提供了程序配置文件,如web.config,App.config,这些配置文件通常会自动生成在程序启动目录下,并具有特定的格式。同时,.Net Framework还提供了一组读写配置文件的接口,这些接口被包含在命名空间System.Configuration中。 但由于接口固定,系统自带的配置文件格式不容易扩展,这些配置文件通常只能用来保存一些简单的信息,比如数据库连接信息或应用程序标识。
    那如何保存复杂的配置信息呢?比如数据持久化、系统界面布局,或者是程序运行过程中产生的临时数据等,这些信息通常都需要自己定义对应的类和结构,多个类之间还存在组合、连接等关系。
    一般的方法是:实现一个解析类,用来存取要保存的数据。在实现读写方法时,需要逐一的解析XML文件的节点,并在解析每个节点过程中找到对应的对象,分别赋值。此时,解析类相当于一个控制器(Controller),负责与所有数据对象(类)的交互,并组织它们的存储结构。示意图如下:
     
    由于解析类和数据对象类之间存在紧耦合,存在以下不足:
    1.代码无法复用。如果有新的数据对象需要配置,还得重新实现一个解析类。
    2.解析类的维护成本加大。每个数据对象类的变化和更改,都需要修改解析类中的相应部分,从而增加维护成本。
    3.不利于数据对象扩展。如果有新的数据也需要通过配置保存,则需要修改解析类,并调整和组织所有数据对象的存储结构。
      
    基于此,不断尝试将解析类和数据对象类之间解耦,以降低代码维护成本,并希望最大程度的复用代码。通过多次的项目实践,发现如下思路:
    1.各个数据对象负责解析自己所对应的XML节点,实现对该节点的写入和读取(序列化和反序列化)。
    2.对象只负责解析自己,如果有子对象,则要求子对象具备自我解析接口,供上级对象调用。
    3.最终的与XML文件的接口由最大一级数据对象来实现,外部通过该接口来实现XML配置文件的读写。
      
    下面以一个小示例来说明,这个示例中假设要使用XML文件来配置数据库信息,数据库信息共有三类数据:数据库,数据表,列,其中,每个数据库有名称和创建日期等信息,包含0或多个数据表;数据表有名表和描述信息,由1至多个列构成;列的信息由列名、数据类型、是否为空等常用属性构成。这个示例可能没有多少实际意义(除非是用于数据持久化,但通常是不用我们自己实现了:)),只用于说明当多种数据之间具有组合关系时,如何实现较灵活的XML读写功能。类图如下:
     
    在示例中,首先定义一个读写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配置方法呢?请看下回分解。
     
    示例代码:下载
  • 相关阅读:
    面向对象程序设计简介(1/2)
    iOS官方Sample大全
    AFN不支持 "text/html" 的数据的问题:unacceptable content-type: text/html
    谈ObjC对象的两段构造模式
    关于self和super在oc中的疑惑与分析 (self= [super init])
    在Xcode中使用Git进行源码版本控制
    NSObject之二
    NSObject之一
    Objective-C Runtime 运行时之六:拾遗
    Objective-C Runtime 运行时之五:协议与分类
  • 原文地址:https://www.cnblogs.com/qingteng1983/p/1756627.html
Copyright © 2011-2022 走看看