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

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

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


事件背景

话说最近换工作了,刚接手的项目的项目中遇到一个棘手的事情;一个第三方组件中使用了老版的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(还有其他辅助类的第三方组件)的时候

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

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

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

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

第三方组件引用另一个第三方组件的悲剧,布布扣,bubuko.com

时间: 2024-11-03 05:36:22

第三方组件引用另一个第三方组件的悲剧的相关文章

Vue 一个组件引用另一个组件

有些时候需要这么做,比如,我想在首页加载轮播组件,但是又不想全局注册(因为不是每个页面都需要轮播功能) 方法1: 1 <template> 2 <div> 3 <!-- 3.在template中就可以直接使用了 --> 4 <testComponent></testComponent> 5 </div> 6 </template> 7 8 <script> 9 //1.先使用import导入你要在该组件中使用的

Android UI开源组件库BottomView ,第三方自定义UI控件

这里分享一个Android的非常经典实用而且简单方便的第三方UI控件库:BottomView(小米的米UI也用到了这个) 实现功能: 可以在底部弹出的View里自定义布局: 可以自定义是否可以触摸外部消失: 可以自定义事件: 可以自定义外围背景是否透明: 可以自定义动画: 如果需要的话,可以强制为顶部View显示 BottomView.jar库文件下载地址:http://download.csdn.net/detail/jay100500/7547055 BottomView的Demo下载地址:

前端Vue框架 04 路由:逻辑跳转、路由传参 项目组件的数据局部化处理data(){ return{} } 组件的声明周期 组件间通信 各种第三方插件(vuex,axios,element-ui,(jq+bs))

项目初始化 """ 1)根组件:App.vue <template> <div id="app"> <router-view /> </div> </template> 2)路由配置:router/index.js const routes = [ { path: '/', name: 'Home', component: Home } ]; 3)组件:views和components文件夹 i)

[UI组件基础] 制作一个带有底部导航以及顶部导航的single view application

最近开始写博客了,把我学习到的东西进行汇总和总结. 很多初学iOS手机应用程序开发的刚开始肯定是抓头的,搞不好,是大片大片的头发往下扯. 因为很迷茫啊,都不知道要怎么弄,有真机有大神帮忙的人还好说,没有机器又没有朋友的人就惨了,完全不知道从何开始. 其实我觉得把,如果是真想学,最次最次,你最少弄个XCode用用看. 前期如果没有XCode,没有苹果电脑,也可以先从基础语法开始学起,其实oc语法也很蛋疼的,什么*啊,alloc啊init啊.估计跟大家想象的世界完全不同,学了java的人再来看oc,

记一个复杂组件(Filter)的从设计到开发

此文前端框架使用 rax,全篇代码暂未开源(待开源) 原文链接地址:Nealyang/PersonalBlog 前言 貌似在面试中,你如果设计一个 react/vue 组件,貌似已经是司空见惯的问题了.本文不是理论片,更多的是自己的一步步思考和实践.文中会有很多笔者的思考过程,欢迎评论区多多交流和讨论. 从需求讨论.技术方案探讨到编码.到最终的测试,经历过了很多次的脑暴,也遇到过非常多的坑,其中有可能跟业务有关.也有可能跟框架有关,基于这些坑,又讨论了很多解决方案和非常 hack(歪门邪道)的对

写一个vue组件

写一个vue组件 我下面写的是以.vue结尾的单文件组件的写法,是基于webpack构建的项目.如果还不知道怎么用webpack构建一个vue的工程的,可以移步到vue-cli. 一个完整的vue组件会包括一下三个部分: template:模板 js: 逻辑 css : 样式 每个组件都有属于自己的模板,js和样式.如果将一个页面比喻成一间房子的话,组件就是房子里的客厅.卧室.厨房.厕所.如果把厨房单独拿出来的话,组件又可以是刀.油烟机...等等.就是说页面是由组件构成的,而组件也可以是组件构成

【转】制作并发布第一个vue组件的npm包

最近在网上找到一个网页制作辅助工具-jQuery标尺参考线插件,觉得在现在的一个项目中能用的上,插件是基于JQuery的,但是现在的项目是用vue写的.So...,就照葫芦画瓢改装成了Vue组件,总的来说算是一个用处较多的组件,于是乎,就想着把它上传到Npm上分享出来.以前只用过别人的包,这一次自己上传一个乐呵乐呵...顺便记录发布一下过程. 项目地址 https://github.com/gorkys/vue... 初始化项目 这里用的是webpack-simple,可以理解为精简版的vue-

如何做一个avalon组件

在avalon1.5中改用更直观的自定义标签来声明组件,废掉ms-widget,引入更强大的生命周期管理,可以让组件任意套嵌. 组件是由JS,HTML,CSS构成 JS 以AMD形式组织,引入HTML与CSS HTML是组件的模板, 模板里面使用ms-*等指令 JS内部是一个avalon.component方法的调用 avalon.component有两个参数,第一个是标签名(务必全部小写并且中间存在冒号,冒号前面是ms, oni等表示UI库的名字,默认ms已经生效,否则要用avalon.lib

使用react context实现一个支持组件组合和嵌套的React Tab组件

纵观react的tab组件中,即使是github上star数多的tab组件,实现原理都非常冗余. 例如Github上star数超四百星的react-tab,其在render的时候都会动态计算哪个tab是被选中的,哪个该被隐藏: getChildren() { let index = 0; let count = 0; const children = this.props.children; const state = this.state; const tabIds = this.tabIds