首先我先聲明,我的摘要是故意這樣寫的,如果你是因為看了摘要才進來的,請讓我大笑三聲:哈哈哈~~
不過既然你已經(jīng)進來了,不妨繼續(xù)往下看看~~
話說最近換工作了,剛接手的項目的項目中遇到一個棘手的事情;一個第三方組件中使用了老版的log4net(1.2.10),另一個第三方組件中使用了新版的log4net(1.2.13)
這下問題來了
當我自己的項目中需要同時使用這2個第三方組件的時候,他們各自引用的log4net版本是不一致的
所以,不管我引用的是哪個版本的log4net,最終的效果是另一個組件初始化的時候?qū)伋霎惓?/span>
如下圖:

由于2個都是非開源的項目,所以無法重新編譯,好在其中一個組件是有技術(shù)支持的,所以聯(lián)系了他們的服務(wù)人員
經(jīng)過一些交涉,問題算是初步解決了

服務(wù)還是非常好的!!贊一個!!!!
不過從最后一句話可以看出,其實最終的原因,還是出在設(shè)計上!!

沒錯~我承認log4net確實是一款不錯的log組件,但是即使是不錯也不是必要的,不可或缺的!
想想JQuery,多么好的一個js組件,依然有很多公司沒有使用jquery,依賴于jquery的往往被稱為jquery插件,因為一旦jquery失效了(或沒引用),你的組件就無法使用了
所以在開發(fā)自己的組件的時候,就需要定位清楚!
這套組件到底是log4net的插件,還是功能獨立的???是否沒有了log4net,組件就無法工作了??
即使它工作再強大,也是輔助而已,并不是不可或缺的!
假設(shè)現(xiàn)在有一個第三方組件,使用上沒有難度
static void Main(string[] args){ //初始化第三方控件(讀取配置文件等操作) SendMessage sm = new SendMessage(); //設(shè)置參數(shù) sm.Arg1 = "1"; sm.Arg2 = "2"; sm.Arg3 = "3"; sm.Arg4 = "4"; //執(zhí)行方法,獲取返回值 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("發(fā)送請求"); Log.InfoFormat("參數(shù)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("出現(xiàn)異常",ex); throw; } }}
就有可能會出現(xiàn)我第一節(jié)中說的情況
并且,這個SendMessage和log4net是高度耦合的!
這個是最容易實現(xiàn)的
我們先把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("發(fā)送請求"); Log.Info(string.Format("參數(shù)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("出現(xiàn)異常", ex); } throw; } }}
這樣非常簡單的就把日志的操作權(quán)交出去了,讓調(diào)用者自己去考慮怎樣完成
嗯,其實我只想調(diào)試一下,并不想持久化日志信息,所以我可以這么干
static void Main(string[] args){ //初始化第三方控件(讀取配置文件等操作) SendMessage sm = new SendMessage(new MyLog()); //設(shè)置參數(shù) sm.Arg1 = "1"; sm.Arg2 = "2"; sm.Arg3 = "3"; sm.Arg4 = "4"; //執(zhí)行方法,獲取返回值 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()); }}
如果使用者希望繼續(xù)使用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); }}
其實這個時候已經(jīng)很好的和log4net解耦了!!
到這里,如果不想了解系統(tǒng)的Trace和Debug類的,就可以直接跳到結(jié)束語了
話說回來,我是一個很懶的人,能少定義一個類,盡量少定義一個類
所以我可以不用定義ILog接口,因為系統(tǒng)已經(jīng)為我們提供的相應(yīng)的對象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("發(fā)送請求"); Log.Write(string.Format("參數(shù)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, "出現(xiàn)異常"); } throw; } }}
現(xiàn)在我已經(jīng)把ILog這個接口給干掉了
所以用戶在使用組件的時候,就需要繼承TraceListener了
class MyLog : TraceListener{ //必須實現(xiàn) public override void Write(string message) { this.WriteLine("info:" + message); } //必須實現(xiàn) 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)是必須實現(xiàn)的
其他都可以選擇重寫
比如,當你沒有重寫Write(object o, string category)的時候,就會調(diào)用Write(string message)方法

剛才在SendMessage中出現(xiàn)了很多
if (Log != null){ Log.Write(message);}
而且這還只是一個演示的項目,真實的項目中會更多這樣的東西,所以必須封裝方法
public string Send() { try { Info("發(fā)送請求"); Info(string.Format("參數(shù)1={0};2={1};3={2};4={3};", Arg1, Arg2, Arg3, Arg4)); string result = null; //..... Info("返回值是:" + result); return result; } catch (Exception ex) { Error("出現(xiàn)異常", 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); } }
之前說了,我是一個很懶的人,能少寫一個方法,就要少寫一個方法
所以,我其實不用封裝方法,直接拿系統(tǒng)方法用就好了,而且連構(gòu)造函數(shù)都省了!
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("發(fā)送請求"); Trace.WriteLine(string.Format("參數(shù)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的參數(shù),這點比較2 //Trace.TraceError("出現(xiàn)異常:" + ex.ToString()); //Trace.Fail("出現(xiàn)異常:" + ex.Message, ex.StackTrace); Trace.WriteLine(ex, "出現(xiàn)異常:"); throw; } }}
既然改了構(gòu)造函數(shù),那么用戶使用的時候也需要修改了
static void Main(string[] args) { //設(shè)置輸出日志組件 Trace.Listeners.Add(new MyLog()); //初始化第三方控件(讀取配置文件等操作) SendMessage sm = new SendMessage(); //設(shè)置參數(shù) sm.Arg1 = "1"; sm.Arg2 = "2"; sm.Arg3 = "3"; sm.Arg4 = "4"; //執(zhí)行方法,獲取返回值 var result = sm.Send(); Console.WriteLine(result); }

class MyLog : TraceListener{ //必須實現(xiàn) public override void Write(string message) { this.WriteLine("info:" + message); } //必須實現(xiàn) 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()); } }}
效果如圖

關(guān)于Trace可以參考文章(利用C#自帶組件強壯程序日志)
寫這篇文章最想要表達的內(nèi)容是:非必要的情況下,第三方組件不應(yīng)該耦合其他第三方組件
這樣的做法就像是在綁架用戶:你用的我的組件,就必須使用xxx,否則我的組件就無法使用
除非你做的是"插件"形式的組件,所以為什么我所有的組件都是開源的,我更希望大家在使用的時候直接將源碼打包的程序中,而不是引用dll,這樣會給你的用戶帶來困擾
這篇文章第二點想做的,就是為之前的文章(利用C#自帶組件強壯程序日志)正名,不知道這樣一番解釋之后,有多少人明白了微軟的Trace模塊,只是一個接口,并不是日志組件
最后我想說的,我并沒有說log4net不好,也沒有提倡大家不使用log4net,只是我希望在使用log4net(還有其他輔助類的第三方組件)的時候
盡可能的對其進行解耦,不要過于依賴(直接引用)
避免一個輔助功能失效(或錯誤),造成整個系統(tǒng)崩潰
引用第三方組件的時候也要注意,盡量使其在一個項目中被引用,然后使用對象或接口進行二次封裝
避免一個組件在所有項目中都存在引用關(guān)系,一樣會造成上面說的一個功能失效(或錯誤),造成整個系統(tǒng)崩潰
新聞熱點
疑難解答