此原则用来解耦合,使高层次的模块不依赖于低层次的模块
这是啥意思呢?
啥是高层次,啥是低层次?
所谓高层次说白了就是抽象,在程序里面对应我们定义的接口,抽象类
所谓低层次对应的是继承抽象类,实现接口的类型。
当然高层低层也有软件结构层次的意思,其实这个高层的结构一样对于低层来说是很抽象的东西,可以用一样的理解方式来理解结构层次的依赖。
啥是高层不依赖低层?
举个栗子:
就像领导总是抽象的,你们给我把事情做完,谁做(依赖接口),你怎么做(接口方法)哥哥我不管,我不依赖你的实现,哥只知道你有这能力(接口),那做实际事情的总是我们这些码农呗(实例化出来的对象),也就是低层。
做个具体实现的栗子,销售部和开发部,销售部在卖东西需要据客户需求添加新功能的时候,他们老板总不能直接跟你技术部的码农直接对话吧?肯定是跟你技术部的老大直接交涉。这个就是高层抽象不依赖低层实现,抽象只依赖抽象。当然具体实现也只依赖抽象。
public interface ISalesman { void AddNewFunction(ICodeFarmer manager); } public class SalesManager : ISalesman { public void AddNewFunction(ICodeFarmer manager) { manager.AddNewFunction(); } } public interface ICodeFarmer { void AddNewFunction(); }
如上代码。应该一目了然了。
那么其实标准的说法是:
1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
2. 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
对于第2点,啥玩意呢?其实不用多解释,就是第一点的补充而已,抽象都依赖抽象了,你具体实现敢不依赖抽象?你销售部老板都只能找我技术部老板谈,你销售部小罗罗当然也只能和我技术部老大提问题!
我再写一个代码:还是上面的栗子,现在把客户的部分也加上来,全部的代码现在如下:
public interface ISalesman { void AddNewFunction(ICodeFarmer manager); } public class SalesManager : ISalesman { public void AddNewFunction(ICodeFarmer manager) { manager.AddNewFunction(); } } public interface ICodeFarmer { void AddNewFunction(); } public class CodeFarmerManager : ICodeFarmer { public void AddNewFunction() { //todo } } public interface IClient { void AddNewFunction(ISalesman salesman); } public class ShaBClient : IClient { private ICodeFarmer CodeFarmerManager; public ShaBClient() { CodeFarmerManager = new CodeFarmerManager(); } public void AddNewFunction(ISalesman salesman) { salesman.AddNewFunction(CodeFarmerManager); } }
现在问题来了,我客户照道理说我只需要和你销售部的打交道,但是实际的代码里面为了调用销售部的方法,需要和搞代码的屌丝也要有依赖
private ICodeFarmer CodeFarmerManager;
客户表示很不开心。这就是第2个常见的错误,依赖了不该依赖的对象。我觉得这也是依赖中的一个常见的问题。
好吧,怎么解决这个问题呢?
简单,在SalesManager构造函数里面初始化一个ICodeFarmer类型就行了
现在代码变成:
public interface ISalesman { void AddNewFunction(); } public class SalesManager : ISalesman { private ICodeFarmer CodeFarmerManager; public SalesManager() { CodeFarmerManager = new CodeFarmerManager(); } public void AddNewFunction() { CodeFarmerManager.AddNewFunction(); } } public interface ICodeFarmer { void AddNewFunction(); } public class CodeFarmerManager : ICodeFarmer { public void AddNewFunction() { //todo } } public interface IClient { void AddNewFunction(); } public class ShaBClient : IClient { public void AddNewFunction() { ISalesman salesman = new SalesManager(); salesman.AddNewFunction(); } }
这样问题貌似解决了,但是!!!!!
突然有一天,技术部的经理表示,哥哥我写代码都没时间,还要整天处理这些和外面打交道的事情,哥哥我不干了。这时公司没办法,只好又招聘了一个人,我们暂且叫技术部助理吧(看这就是千变万化的软件世界)
public class CodeFarmerAssistant : ICodeFarmer { public void AddNewFunction() { //todo } }
销售部门提需求需要跟这位新同事打交道。但是有时候助理他有些问题不明白还是要和技术经理提需求,这里
private ICodeFarmer CodeFarmerManager; public SalesManager() { CodeFarmerManager = new XXXXXXXX(); }
就形成了一个变化的东西,但是代码里面你总要给new一个啥东西出来呀,怎么搞??
好吧,现在只能这样:在构造函数中指明需要和啥玩意打交道
public class SalesManager : ISalesman { private ICodeFarmer codeFarmer; public SalesManager(ICodeFarmer codeFarmer) { this.codeFarmer = codeFarmer; } public void AddNewFunction() { this.codeFarmer.AddNewFunction(); } } //现在客户端代码要随着变化: public class ShaBClient : IClient { public void AddNewFunction() { ISalesman salesman = new SalesManager(new CodeFarmerAssistant()); salesman.AddNewFunction(); } }
这样老问题又出来
尼玛客户发怒了,又要老子和码农打交道
ISalesman salesman = new SalesManager(new CodeFarmerAssistant());
老子再也不想见到码农了,滚!!!
你看这事办的,差点客户都丢了。那咋整啊!!
这里请大家一定要注意一点,所谓的依赖解耦,不是说这玩意没依赖了,而是把依赖从一个地方转移到另一个地方,转移到什么地方呢?能转到什么地方,无非2个地方,因为这2地方好弄
1. 配置文件中。随便就可以改改,不用弄程序
2. 添加中间层,集中将依赖放到这里。这样好集中管理。
我来分别说说这2点怎么弄
弄到配置文件中:
public class SalesManager : ISalesman { private ICodeFarmer codeFarmer; public SalesManager() { this.codeFarmer = Assembly.Load("Core").CreateInstance(ConfigurationManager.AppSettings["CodeFamerRepresent"]) as ICodeFarmer; } public void AddNewFunction() { this.codeFarmer.AddNewFunction(); } }
看,自从用了反射,妈妈再也用担心不能用配置文件创建对象了。老板再也不用担心客户发飙了。
public class ShaBClient : IClient { public void AddNewFunction() { ISalesman salesman = new SalesManager(); salesman.AddNewFunction(); } }
这样只要变更一下配置,就能控制到底谁是码农的代言人
<appSettings>
<add key="CodeFamerRepresent" value="CodeFarmerManager"/>
<!--<add key="CodeFamerRepresent" value="CodeFarmerAssistant"/>-->
</appSettings>
看这不是挺好么?
这里就引入了一个新的词,依赖注入
如上面例子
咱们不仅要知道注入方式(以免违反DI原则),也要知道注入的 时机(以免依赖不需要依赖的对象)
啥玩意依赖注入,这么高深的词,实际上就是尼玛,咋初始化这个被依赖的对象IcodeFarmer,初始化了,依赖产生了,所以注入了依赖,这个词把他理解成 初始化被依赖的对象 更形象一点。关键就是在哪里初始化化这个对象。刚刚那个例子说了可以在构造函数中注入依赖,实际上还有一种属性注入,都不扯那么远了,既然可以通过构造函数注入,尼玛还需要了解属性注入干蛋,哥哥我学东西的原则就是不学多余的。只要明白最根本的道理就行。程序语言的技巧,咱没太多精力玩那个!
那接下来说添加中间层来做这个事情是啥意思,简单呗,工厂模式就是干这个的
我简单写一下就是:
public class CodeFarmerFactory { public static ICodeFarmer GetCodeFarmer() { return new CodeFarmerAssistant(); //return new CodeFarmerManager(); } } public class SalesManager : ISalesman { private ICodeFarmer codeFarmer; public SalesManager() { this.codeFarmer = CodeFarmerFactory.GetCodeFarmer(); } public void AddNewFunction() { this.codeFarmer.AddNewFunction(); } }
有的人说,你这不是扯淡吗?你具体的对象创建你还不是要改代码
public static ICodeFarmer GetCodeFarmer()
{
return new CodeFarmerAssistant();
//return new CodeFarmerManager();
}
我想说的是,你这不废话吗?你要变化东西,你不改就能变化?要么改代码,要么改配置文件,这里只是把变化集中到一起(工厂中)管理而已,不是说尼玛不用变!!!
那是不是说就这样就完了,当然不是,如果是,我还写autofac干蛋。那还有什么需要考虑的事情呢,我现在先总结一下如下几点:
1. 反射性能问题,你不能每次创建一个对象都反射一下,这样影响性能,有时我们需要把对象缓存起来,下次直接取缓存里面的对象,我们可以叫这个缓存为容器,这也是所以IOC容器里都有的一个概念
2. 有了缓存,我们需要管理这个缓存对象的创建和销毁时间问题,不能老存在里面,或者说有些对象会过期,比如一些数据访问对象,当数据连接都释放了,你还保存这个对象,有蛋蛋用,一个访问web页面结束了,你还保存Controller有蛋蛋用?所以有个生命周期的概念。
3. 当缓存对象销毁的时候,引用的非托管资源怎么办!
其他的问题我之后说到autofac的时候再来具体讲,现在一下列出来太突兀,没什么用。
希望通过这一篇文章说明了一些基本的概念,如果有什么有问题的地方希望大家提出讨论