代码改变世界

一个自己写的组件--异常报告(2):组件的重构和配置

2008-07-03 20:40  GUO Xingwang  阅读(1770)  评论(4编辑  收藏  举报

     在上一篇文章中我对异常报告组件MyDebuger做了一个一般性的介绍并用简单的C#对其进行了实现,发表之后网友们提出了一些建议,也提出了一些Bug。在这里,非常感谢来自你们的建议,我从中领略到了社区对于软件开发的重要性,社区的意见是宝贵的。其中有一位朋友说"虽然能看懂但是代码逻辑混乱,应该好好重构下",还有人说那个Debuger.Debug()方法中的obj为空的问题.自己仔细的考虑了一下,可能确实比较混乱,于是在这一篇文章中我将着力讲述我对MyDebuger组件是怎样进行重构的,并对上一节提到的配置问题进行了实现(实现的可能不太好,但是已经基本满足需求了).花了一整天时间,希望这次会好一点。
 

1.组件的重构

       对于组件的重构我主要是在原来的基础上对异常提交处理部分进行了抽象,采用了Factory Method模式(灵感来源与博客园上的一篇叫《解读设计模式----工厂方法模式》,http://www.cnblogs.com/beniao/archive/2008/04/13/1145936.html,作者:beniao).对于异常发掘机部分我采用了一个Singleton模式,个人觉得这两个模式比较适合我的组件,理由是:对于Factory Method我们希望可以通过配置决定采用哪个Creator;这正好是我所需的,对于Singleton模式的应用自然由于系统中只要有一个这样的发掘机就已经足够了(以单例模式运行比较好),再者我增加了一个参数用来记录发现的异常是第几个,这是一个统计参数,有可能输出,于是我就在这个单例中增加一个计数器解决了这个问题的,这也是单例带来的好处.希望这两个模式没有被我滥用。

    重构后的解决方案结构如下:



    
    刚才提到了异常发掘机
,可能有的朋友不是很清楚,那么现在就让我们来认识一下项目中的几个文件(,我开发这个组件时基本上是一个类写到一个文件中,个别除外例如Config.cs)的具体作用吧,其中组件中一个很重要的部分就是异常发掘机(Detector,由于是单例,使用Detector.GetCurentInstance()获得),形如:

Detector __detector = Detector.GetCurentInstance();

Common.cs:这里封装了一个ContextWraper类型,主要用于异常消息的传递方便。

Debuger.cs:属于最高层的使用部分,收集相应的异常信息.当方法捕获到异常时使用Debuger.Debug()方法进行处理就可以了。

Detector.cs:这就是所谓的异常发掘机,实际上它是一个Singleton,里面封装了异常消息的处理,对外提供事件接口,用户可以自定义处理程序,当系统中出现异常时,Debuger.Debug()被调用时(或其它方式)将会触发事件,被触发的事件由用户自定义的处理程序进行处理,例如:

 1private void CatchExceptionMethod2(string para1, int para2, Person para3)
 2        {
 3            try
 4            {
 5                throw new Exception("这是一个异常");
 6            }

 7            catch (Exception e)
 8            {
 9                Object[] obj = new Object[] { para1, para2, para3 };
10
11                Detector __detector = Detector.GetCurentInstance();
12                __detector.NewExceptionOccurEvent += new Detector.ExceptionHandler(NewExceptionOccurEventHandler);
13
14                //报告异常
15                Debuger.Debug(e, obj);
16            }

17        }

 1private void NewExceptionOccurEventHandler(object sender, EventArgs e)
 2        {
 3            Detector __detector = (Detector)sender;
 4            ExceptionContextEventArgs __exceptionContextEventArgs=(ExceptionContextEventArgs)e;
 5
 6            Console.Write("抛出异常的方法名是:{0};\n参数列表是:{1};\n异常:{2};\n",
 7                    __detector.CurrentExceptionContext.Method, __detector.CurrentExceptionContext.GetParametersList(),
 8                    __detector.CurrentExceptionContext.Exception.Message);
 9
10            Console.Write("索引号:{0};时间:{1}\n" ,__exceptionContextEventArgs.OccurIndex ,
11                    __exceptionContextEventArgs.OccurTime.ToString());
12        }

在处理模式上通过配置可以采用兼容异常处理程序方式进行处理,即自定义处理程序和组件提供的处理程序同时进行或只有自定义处理程序执行。此外异常发掘机对外还提供了一个操作默认异常处理程序的操作接口。

IOutput.csOutputCreator.cs实现了Factory Method的抽象部分,我们需要的处理程序实现IOutput接口,再提供一个相应的Creator,最后通过配置文件指定就可以与组件的主体部分一起工作了。

EventLogsOutputCreator.csEventLogsOutput.cs是上面的抽象部分的一种实现,同时也是MyDebuger的默认异常处理程序。

ExceptionContextEventArgs.cs:这个我想就不用说了吧。

Config.cs:配置处理部分,主要是提取出配置项并以强类型方式提供给程序使用,文件里定义了一个类型转换器,下面会具体介绍。



2.组件的配置

    配置可以给软件带来灵活性和可扩展性,通过配置的都属于变化的部分,当用户需求改变时,我们不用修改代码和重新编译,只要简单的修改一下配置文件就可以了。配置文件一般做成软件和人都可以看懂的文件,一般以XML文件存储比较适合,配置项的选取很重要,如果一个配置项一直没有修改过,那就还不如约定好,约定在一定条件下确实胜于配置(约定胜于配置).哈哈,扯得有点远了。

       .Net平台下应用程序的配置部分是比较繁琐的,如果不好好研究一下真的很难弄懂,它的配置处理一般使用System.Configuration名称空间,但是在引用里显示的是System.configuration,字母c是小写的,不知道微软为什么这么做?哈哈,甭管它了。先看看我的配置是怎么做的吧!根据MyDebuger组件的需求,我目前设计了如下配置项:

1<myDebugerSettings defaultOutputApply="true">
2    <outputCreators>
3      <add name="eventLogsOutput" outputCreator="MyDebuger,EventLogsOutputCreator,MyDebuger" />
4    </outputCreators>
5  </myDebugerSettings>
6</configuration>

其中各个部分的含义如下:
 

Attribute

Type

description

default

defaultOutputApply

bool

用来配置outputCreators中的Creator是否生效,主要是为用户已经对发掘机事件进行了处理之后还是否保留默认的处理方式,true时表示同时生效,false表示只有用户程序生效

true

outputCreators

outputCreatorsCollection

异常发掘机采用的所有处理程序的挂接

Windows事件日志处理

name

string

处理程序的名称

outputCreator

OutputCreator

处理程序,格式如下:“名称空间,类名称,程序集”

Add

Remove

Clear

string

标准的.Net配置节点的增删,清空方式


为了这个配置还要写上几个类(好像有个scdl.exe工具专门可以生成代码,不过没有找到,干脆自己写一个吧)

  1using System;
  2using System.Configuration;
  3using System.Reflection;
  4using System.ComponentModel;
  5using System.Globalization;
  6
  7namespace MyDebuger
  8{
  9    //强类型获得配置
 10    public class OutputCreatorsElement : ConfigurationElement
 11    {
 12        [ConfigurationProperty("name", DefaultValue = "", IsKey = true, IsRequired = true)]
 13        public string Name
 14        {
 15            get
 16            {
 17                return ((string)(base["name"]));
 18            }

 19        }

 20
 21        //使用了自定义的OutputCreatorConverter进行类型转换
 22        [ConfigurationProperty("outputCreator", DefaultValue = "MyDebuger,EventLogsOutputCreator,MyDebuger", IsKey = true, IsRequired = true)]
 23        [TypeConverter(typeof(OutputCreatorConverter))]
 24        public OutputCreator OutputCreator
 25        {
 26            get
 27            {
 28                return (OutputCreator)this["outputCreator"];
 29            }

 30        }

 31    }

 32
 33    [ConfigurationCollectionAttribute(typeof(OutputCreatorsElement))]
 34    public class OutputCreatorsCollection : ConfigurationElementCollection
 35    {
 36        protected override ConfigurationElement CreateNewElement()
 37        {
 38            return new OutputCreatorsElement();
 39        }

 40
 41        protected override object GetElementKey(ConfigurationElement element)
 42        {
 43            return ((OutputCreatorsElement)(element)).Name;
 44        }

 45
 46        public void Add(OutputCreatorsElement element)
 47        {
 48            this.BaseAdd(element);
 49        }

 50
 51        public void Remove(string key)
 52        {
 53            this.BaseRemove(key);
 54        }

 55
 56        public void Clear()
 57        {
 58            this.BaseClear();
 59        }

 60    }

 61
 62    public class MyDebugerSettingsSection : ConfigurationSection
 63    {
 64        [ConfigurationProperty("defaultOutputApply", DefaultValue = "true", IsKey = false, IsRequired = false)]
 65        public bool DefaultOutputApply
 66        {
 67            get
 68            {
 69                return ((bool)(base["defaultOutputApply"]));
 70            }

 71        }

 72
 73        [ConfigurationProperty("outputCreators")]
 74        public OutputCreatorsCollection OutputCreators
 75        {
 76            get
 77            {
 78                return ((OutputCreatorsCollection)(base["outputCreators"]));
 79            }

 80        }

 81    }

 82
 83    //自定义的OutputCreatorConverter类型转换器
 84    public sealed class OutputCreatorConverter : ConfigurationConverterBase
 85    {
 86        public override bool CanConvertTo(ITypeDescriptorContext ctx, Type type)
 87        {
 88            return false;
 89        }

 90
 91        public override bool CanConvertFrom(ITypeDescriptorContext ctx, Type type)
 92        {
 93            return (type == typeof(string));
 94        }

 95
 96        public override object ConvertTo(ITypeDescriptorContext ctx, CultureInfo ci, object value, Type type)
 97        {
 98            return "";
 99        }

100
101        //字符串类型到OutputCreator的转换
102        public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data)
103        {
104            string typeFullName = (string)data;
105            char ch = ',';
106            string[] _tempFull = typeFullName.Split(ch);
107            if (_tempFull.Length != 3)
108                throw new InvalidOperationException("OutputCreator is not correct");
109            string creatorName = _tempFull[1].Trim();
110            string assemblyName = _tempFull[2].Trim();
111            string className = _tempFull[0].Trim() + "." + creatorName;
112            OutputCreator __outputCreator;
113            try
114            {
115                __outputCreator = (OutputCreator)Assembly.Load(assemblyName).CreateInstance(className);
116            }

117            catch (Exception ex)
118            {
119                throw ex;
120            }

121            return __outputCreator;
122        }

123    }

124}

125

其中OutputCreatorConverter用于从上面的配置项中将OutputCreator生成强类型的OutputCreator而提供的类型转换器.这样使用就行了。


配置的使用很简单
,我们只需要在应用程序(Winform,Console,WebForm,WS等都没问题)App.configWeb.config文件中增加下面的配置部分就可以了:

 1<?xml version="1.0" encoding="utf-8" ?>
 2<configuration>
 3  <configSections>
 4    <section name="myDebugerSettings" type="MyDebuger.MyDebugerSettingsSection, MyDebuger" />
 5  </configSections>
 6  <myDebugerSettings defaultOutputApply="true">
 7    <outputCreators>
 8      <add name="eventLogsOutput" outputCreator="MyDebuger,EventLogsOutputCreator,MyDebuger" />
 9    </outputCreators>
10  </myDebugerSettings>
11</configuration>


其中
type="MyDebuger.MyDebugerSettingsSection, MyDebuger"为定义在Config.cs中的配置处理类,说明使用强类型进行了处理。 

    这个组件已经实现了,源代码提供给大家,如果有时间我可以写一个异常处理程序,然后通过配置与这个组件一起工作。感兴趣的朋友也可以自己实现。

总结:在做面向对象的软件的设计时,最重要的一点就是封装变化点,哪里变化封装哪里!


组件源代码和Demo测试:
MyDebuger_SRC20080703.RAR