• 第三方组件引用另一个第三方组件的悲剧


    首先我先声明,我的摘要是故意这样写的,如果你是因为看了摘要才进来的,请让我大笑三声:哈哈哈~~

    不过既然你已经进来了,不妨继续往下看看~~

    事件背景

    话说最近换工作了,刚接手的项目的项目中遇到一个棘手的事情;一个第三方组件中使用了老版的log4net(1.2.10),另一个第三方组件中使用了新版的log4net(1.2.13)

    这下问题来了

    当我自己的项目中需要同时使用这2个第三方组件的时候,他们各自引用的log4net版本是不一致的

    所以,不管我引用的是哪个版本的log4net,最终的效果是另一个组件初始化的时候将抛出异常

    如下图:

    由于2个都是非开源的项目,所以无法重新编译,好在其中一个组件是有技术支持的,所以联系了他们的服务人员

    经过一些交涉,问题算是初步解决了

    服务还是非常好的!!赞一个!!!!

    不过从最后一句话可以看出,其实最终的原因,还是出在设计上!!

    为什么一定要耦合log4net?

    没错~我承认log4net确实是一款不错的log组件,但是即使是不错也不是必要的,不可或缺的!

    想想JQuery,多么好的一个js组件,依然有很多公司没有使用jquery,依赖于jquery的往往被称为jquery插件,因为一旦jquery失效了(或没引用),你的组件就无法使用了

    所以在开发自己的组件的时候,就需要定位清楚!

    这套组件到底是log4net的插件,还是功能独立的???是否没有了log4net,组件就无法工作了??

    即使它工作再强大,也是辅助而已,并不是不可或缺的!

    第三方组件的日志设计

    假设现在有一个第三方组件,使用上没有难度

    static void Main(string[] args)
    {
        //初始化第三方控件(读取配置文件等操作)
        SendMessage sm = new SendMessage();
    
        //设置参数
        sm.Arg1 = "1";
        sm.Arg2 = "2";
        sm.Arg3 = "3";
        sm.Arg4 = "4";
                
        //执行方法,获取返回值
        var result = sm.Send();
    
        Console.WriteLine(result);
    }

    但是如果SendMessage是这样写的

    public class SendMessage
    {
        ILog Log;
    
        public SendMessage()
        {
            Log = log4net.LogManager.GetLogger(typeof(SendMessage));
            Log.Info("初始化完成");
        }
    
        public string Arg1 { get; set; }
        public string Arg2 { get; set; }
        public string Arg3 { get; set; }
        public string Arg4 { get; set; }
    
        public string Send()
        {
            try
            {
                Log.Info("发送请求");
                Log.InfoFormat("参数1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4);
                string result = null;
                //.....
                Log.Info("返回值是:" + result);
                return result;
            }
            catch (Exception ex)
            {
                Log.Error("出现异常",ex);
                throw;
            }
        }
    }

    就有可能会出现我第一节中说的情况

    并且,这个SendMessage和log4net是高度耦合的!

    更换自己的接口

    这个是最容易实现的

    我们先把log4net抛弃~然后自己声明一个ILog接口

    public interface ILog
    {
        void Info(string message);
        void Debug(string message);
        void Warn(string message);
        void Error(string caption, Exception ex);
    }

    然后替换到SendMessage中

    public class SendMessage
    {
        ILog Log;
    
        public SendMessage(ILog log)
        {
            Log = log;
            if (log != null)
            {
                Log.Info("初始化完成");
            }
        }
    
        public SendMessage()
        {
    
        }
    
        public string Arg1 { get; set; }
        public string Arg2 { get; set; }
        public string Arg3 { get; set; }
        public string Arg4 { get; set; }
    
        public string Send()
        {
            try
            {
                if (Log != null)
                {
                    Log.Info("发送请求");
                    Log.Info(string.Format("参数1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4));
                }
                string result = null;
                //.....
    
                if (Log != null)
                {
                    Log.Info("返回值是:" + result);
                }
                return result;
            }
            catch (Exception ex)
            {
                if (Log != null)
                {
                    Log.Error("出现异常", ex);
                }
                throw;
            }
        }
    }

    这样非常简单的就把日志的操作权交出去了,让调用者自己去考虑怎样完成

    嗯,其实我只想调试一下,并不想持久化日志信息,所以我可以这么干

    static void Main(string[] args)
    {
        //初始化第三方控件(读取配置文件等操作)
        SendMessage sm = new SendMessage(new MyLog());
    
        //设置参数
        sm.Arg1 = "1";
        sm.Arg2 = "2";
        sm.Arg3 = "3";
        sm.Arg4 = "4";
    
        //执行方法,获取返回值
        var result = sm.Send();
    
        Console.WriteLine(result);
    }
    
    class MyLog : ILog
    {
        public void Info(string message)
        {
            Console.WriteLine("info:" + message);
        }
    
        public void Debug(string message)
        {
            Console.WriteLine("debug:" + message);
        }
    
        public void Warn(string message)
        {
            Console.WriteLine("warn:" + message);
        }
    
        public void Error(string caption, Exception ex)
        {
            Console.WriteLine("error:" + caption);
            Console.WriteLine("error:" + ex.ToString());
        }
    }

    如果使用者希望继续使用log4net当然也是没有问题的

    class MyLog : ILog
    {
        log4net.ILog Log;
        public MyLog()
        {
            Log = log4net.LogManager.GetLogger(typeof(MyLog));
        }
        public void Info(string message)
        {
            Log.Info(message);
        }
    
        public void Debug(string message)
        {
            Log.Debug(message);
        }
    
        public void Warn(string message)
        {
            Log.Warn(message);
        }
    
        public void Error(string caption, Exception ex)
        {
            Log.WriteLine(caption, ex);
        }
    }

    其实这个时候已经很好的和log4net解耦!!

    到这里,如果不想了解系统的Trace和Debug类的,就可以直接跳到结束语

    使用系统对象(接口/抽象类)

    话说回来,我是一个很懒的人,能少定义一个类,尽量少定义一个类

    所以我可以不用定义ILog接口,因为系统已经为我们提供的相应的对象TraceListener该有的方法都有了

    所以我直接这么干!

    public class SendMessage
    {
        TraceListener Log;
    
        public SendMessage(TraceListener log)
        {
            Log = log;
            if (log != null)
            {
                Log.Write("初始化完成");
            }
        }
    
        public SendMessage()
        {
    
        }
    
        public string Arg1 { get; set; }
        public string Arg2 { get; set; }
        public string Arg3 { get; set; }
        public string Arg4 { get; set; }
    
        public string Send()
        {
            try
            {
                if (Log != null)
                {
                    Log.Write("发送请求");
                    Log.Write(string.Format("参数1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4));
                }
                string result = null;
                //.....
    
                if (Log != null)
                {
                    Log.Write( "返回值是:" + result);
                }
                return result;
            }
            catch (Exception ex)
            {
                if (Log != null)
                {
                    Log.Write(ex, "出现异常");
                }
                throw;
            }
        }
    }

    现在我已经把ILog这个接口给干掉了

    所以用户在使用组件的时候,就需要继承TraceListener了

    class MyLog : TraceListener
    {
        //必须实现
        public override void Write(string message)
        {
            this.WriteLine("info:" + message);
        }
        //必须实现
        public override void WriteLine(string message)
        {
            Console.WriteLine(message);
        }
        //可重写,可不写
        public override void Write(object o, string category)
        {
            if (o is Exception)
            {
                Console.WriteLine("error:" + category);
                Console.WriteLine("error:" + o.ToString());
            }
            else
            {
                this.WriteLine(category + ":" + o.ToString());
            }
        }
    }

    其中只有void Write(string message)和void WriteLine(string message)是必须实现的

    其他都可以选择重写

    比如,当你没有重写Write(object o, string category)的时候,就会调用Write(string message)方法

    封装方法

    刚才在SendMessage中出现了很多

    if (Log != null)
    {
        Log.Write(message);
    }

    而且这还只是一个演示的项目,真实的项目中会更多这样的东西,所以必须封装方法

            public string Send()
            {
                try
                {
                    Info("发送请求");
                    Info(string.Format("参数1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4));
                    string result = null;
                    //.....
                    Info("返回值是:" + result);
                    return result;
                }
                catch (Exception ex)
                {
                    Error("出现异常", ex);
                    throw;
                }
            }
    
            private void Info(string message)
            {
                if (Log != null)
                {
                    Log.Write(message);
                }
            }
    
            private void Error(string caption, Exception ex)
            {
                if (Log != null)
                {
                    Log.Write(ex, caption);
                }
            }

    使用系统的方法

    之前说了,我是一个很懒的人,能少写一个方法,就要少写一个方法

    所以,我其实不用封装方法,直接拿系统方法用就好了,而且连构造函数都省了!

    public class SendMessage
    {
        public SendMessage()
        {
            Trace.WriteLine("初始化完成");
        }
    
        public string Arg1 { get; set; }
        public string Arg2 { get; set; }
        public string Arg3 { get; set; }
        public string Arg4 { get; set; }
    
        public string Send()
        {
            try
            {
                Trace.WriteLine("发送请求");
                Trace.WriteLine(string.Format("参数1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4));
                string result = null;
                //.....
                Trace.WriteLine("返回值是:" + result);
                return result;
            }
            catch (Exception ex)
            {
                //3种方式都可以,但是只有 WriteLine 可以接受object的参数,这点比较2
                //Trace.TraceError("出现异常:" + ex.ToString());
                //Trace.Fail("出现异常:" + ex.Message, ex.StackTrace);
                Trace.WriteLine(ex, "出现异常:");
                throw;
            }
        }
    }

    既然改了构造函数,那么用户使用的时候也需要修改了

            static void Main(string[] args)
            {
                //设置输出日志组件
                Trace.Listeners.Add(new MyLog());
                //初始化第三方控件(读取配置文件等操作)
                SendMessage sm = new SendMessage();
    
                //设置参数
                sm.Arg1 = "1";
                sm.Arg2 = "2";
                sm.Arg3 = "3";
                sm.Arg4 = "4";
    
                //执行方法,获取返回值
                var result = sm.Send();
    
                Console.WriteLine(result);
            }
    class MyLog : TraceListener
    {
        //必须实现
        public override void Write(string message)
        {
            this.WriteLine("info:" + message);
        }
        //必须实现
        public override void WriteLine(string message)
        {
            Console.WriteLine(message);
        }
        //可重写,可不写
        public override void Write(object o, string category)
        {
            if (o is Exception)
            {
                Console.WriteLine("error:" + category);
                Console.WriteLine("error:" + o.ToString());
            }
            else
            {
                this.WriteLine(category + ":" + o.ToString());
            }
        }
    }
    MyLog依然不变

    效果如图

    关于Trace可以参考文章(利用C#自带组件强壮程序日志)

    结束语

    写这篇文章最想要表达的内容是:非必要的情况下,第三方组件不应该耦合其他第三方组件

    这样的做法就像是在绑架用户:你用的我的组件,就必须使用xxx,否则我的组件就无法使用

    除非你做的是"插件"形式的组件,所以为什么我所有的组件都是开源的,我更希望大家在使用的时候直接将源码打包的程序中,而不是引用dll,这样会给你的用户带来困扰

    这篇文章第二点想做的,就是为之前的文章(利用C#自带组件强壮程序日志)正名,不知道这样一番解释之后,有多少人明白了微软的Trace模块,只是一个接口,并不是日志组件

    最后我想说的,我并没有说log4net不好,也没有提倡大家不使用log4net,只是我希望在使用log4net(还有其他辅助类的第三方组件)的时候

    尽可能的对其进行解耦,不要过于依赖(直接引用)

    避免一个辅助功能失效(或错误),造成整个系统崩溃

    引用第三方组件的时候也要注意,尽量使其在一个项目中被引用,然后使用对象或接口进行二次封装

    避免一个组件在所有项目中都存在引用关系,一样会造成上面说的一个功能失效(或错误),造成整个系统崩溃

  • 相关阅读:
    Spring:dispatchservlet
    信息系统设计
    数据流图的绘制方法
    信息系统管理工程师学习笔记
    JS语法学习笔记
    jQuery
    用Excel生成Sql
    JAVA-Reflect
    Java创建对象的过程
    有关死锁那点事儿
  • 原文地址:https://www.cnblogs.com/blqw/p/3726493.html
Copyright © 2020-2023  润新知