前言
文中内容是结合多篇文章的学习笔记,记录下来以免时间久了忘记。
面向对象设计(OOD)有助于我们开发出高性能、易扩展以及易复用的程序。其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IOC、DI以及IOC容器等概念。通过本文我们将一起学习这些概念,并理清他们之间微妙的关系。
概念
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念),依赖抽象而不是依赖细节。
控制反转(IOC):一种反转流、依赖和接口的方式(DIP的具体实现方式),通过抽象获取细节,实现这个动作的时候就是控制反转。
依赖注入(DI):IOC的一种实现方式,用来反转依赖(IOC的具体实现方式),DI是实现IOC的一种技术手段。
IOC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架),IOC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:
- 动态创建、注入依赖对象。
- 管理对象生命周期。
- 映射依赖关系。
图示
依赖
以三层架构为例,从上到下,UI->BLL->DAL这就是依赖,这种最基本的依赖是依赖于细节的,耦合度较高,DAL如果修改了代码,则底层到高层的代码都需要进行改变。所以需要进行解耦,由此引出依赖倒置原则(DIP)。
依赖倒置DIP
耦合关系就是依赖关系,如果依赖关系相当繁杂,牵一发而动全身,很难维护;依赖关系越少,耦合关系就越低,系统就越稳定,所以我们要减少依赖。
幸亏Robert Martin大师提出了面向对象设计原则----依赖倒置原则:
- A. 高层模块不应依赖于低层模块,两者应该依赖于抽象。
- B. 抽象不应该依赖于实现,实现应该依赖于抽象。
理解:A.上层是使用者,下层是被使用者,这就导致的结果是上层依赖下层了,下层变动了,自然就会影响到上层了,导致系统不稳定,甚至是牵一发而动全身。那怎么减少依赖呢?就是上层和下层都去依赖另一个抽象,这个抽象比较稳定,整个就来说就比较稳定了。
B.面向对象编程时面向抽象或者面向借口编程,抽象一般比较稳定,实现抽象的具体肯定是要依赖抽象的,抽象不应该去依赖别的具体,应该依赖抽象。
依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。通俗的讲,就是高层模块定义接口,低层模块负责实现。
控制反转IOC
DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IOC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IOC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。
控制反转IOC是Inversion of Control的缩写,是说对象的控制权进行转移,转移到第三方,比如转移交给了IOC容器,它就是一个创建工厂,你要什么对象,它就给你什么对象,有了IOC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IOC容器了,通过IOC容器来建立它们之间的关系。
软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。一些常见的原则,比如DRY、OCP、DIP等。
软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等。
依赖注入(DI)
上面说到控制反转,是一个思想概念,但是也要具体实现的,上面的配置文件也是一种实现方式。依赖注入提出了具体的思想。
依赖注入DI是Dependency Injection缩写,它提出了“哪些东东的控制权被反转了,被转移了?”,它也给出了答案:“依赖对象的创建获得被反转”。
所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。
依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。
IOC容器
IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的,很多的成熟的框架,比如象Java中的hibernate、Spring框架,.Net中 NHibernate、Spring.NET框架都是把“反射”做为最基本的技术手段。
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
动手实现一个无限层级的IOC容器
如果觉得代码太多不易阅读,可以直接阅读核心代码,只有简单的三步结合对应的概念和最开始的图示,可以很快在脑海中理清基本思路。
IFishServiceCollection
public interface IFishServiceCollection
{
/// <summary>
/// 添加具有实现的在serviceType中指定的类型的临时服务
/// 在ImplementationType中指定的类型为指定的Microsoft.Extensions.DependencyInjection.IServiceCollection。
/// </summary>
/// <param name="serviceType">要注册的服务的类型</param>
/// <param name="implementationType">服务的实现类型</param>
/// <param name="shortName">服务的实现类型别名</param>
void AddTransient(Type serviceType,Type implementationType,string shortName =null);
/// <summary>
/// 获取指定类型的服务对象
/// </summary>
/// <typeparam name="T">要获取的服务类型</typeparam>
/// <returns>生产的服务</returns>
T GetService<T>(string shortName=null);
}
FishServiceCollection
public class FishServiceCollection : IFishServiceCollection
{
private Dictionary<string, Type> richarDictionary = new Dictionary<string, Type>();
//拼接要注册的服务的类型和别名
private string GetKey(Type type, string shortName) => $"{type.FullName}___{shortName}";
//{
// return $"{type.FullName}___{shortName}";
//}
/// <summary>
/// 注册服务--其实就是把抽象和具体的映射关系保存起来
/// </summary>
/// <param name="serviceType">要注册的服务的类型(抽象:接口等)</param>
/// <param name="implementationType">服务的实现类型(细节:实例)</param>
/// <param name="shortName">别名</param>
public void AddTransient(Type serviceType, Type implementationType,string shortName)
{
//使用别名完成多实现功能
string Key = GetKey(serviceType,shortName);
richarDictionary.Add(Key, implementationType);
//01 将对象与细节储存在字典内
//richarDictionary.Add(serviceType.FullName,implementationType);
}
/// <summary>
/// 获取服务实例
/// </summary>
/// <typeparam name="T">要获取的服务类型(抽象:接口)</typeparam>
/// <returns>生产的服务</returns>
public T GetService<T>(string shortName)
{
#region 第一版 简单层级
////02 通过字典取出具体的类型
//Type type = richarDictionary[typeof(T).FullName];
////03 通过反射创建实例
////return (T)Activator.CreateInstance(type);//如果需要构造的对象中构造函数参数 不为空;就报错了 调用的无参数的构造函数
////1.先取构造 当前类构造函数中需要的参数的对象 确定构造当前对象使用那个构造函数(默认选择参数最多的构造函数)
//var contrArray = type.GetConstructors();//获取Type的所有构造函数
////2.根据构造函数参数数量降序配列,再取第一个==获取到构造函数参数最多的这一个
//var ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
////3.准备构造函数的参数
//List<object> prarlist = new List<object>();
//foreach (ParameterInfo para in ctor.GetParameters())
//{
// Type paraType = para.ParameterType;
// Type parTargetType = richarDictionary[paraType.FullName];
// #region 思路引导
// //1* 确定使用那个构造函数
// //var contrArray1 = parTargetType.GetConstructors();
// //2* 根据构造函数参数数量降序排序,再取第一个==获取到构造函数参数最多的这一个
// //var ctor1 = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
// //3* 准备构造函数的参数········
// //不知道要构造的对象的构造函数具体依赖多少个层级;
// //永远写不完
// //怎么办?使用递归
// #endregion
// var target = Activator.CreateInstance(parTargetType);
// prarlist.Add(target);
//}
////4.带参数的构造当前对象
//return (T)Activator.CreateInstance(type, prarlist.ToArray());
#endregion
#region 多层级
Type type = richarDictionary[GetKey(typeof(T), shortName)];
return (T)this.GetService(type);
#endregion
}
private object GetService(Type type)
{
#region 构造函数注入
//1.先去构造 当前类构造函数中需要的参数的对象 确定构造当前对象使用那个构造函数(默认选择参数最多的构造函数)
var contrArray = type.GetConstructors();//获取Type的所有构造函数
//2.根据构造函数参数数量降序配列,再取第一个==获取到构造函数参数最多的这一个
//2.1 这是默认情况下,选择构造函数参数最多的,但是有特殊情况,如果就需要选择一个自己指定的构造函数呢?
//2.2 需求:需要通过自己指定选择不同的构造函数,如果没有指定,就选择默认
//2.3 如何解决呢?=>通过特性解决
//2.4 通过特性支持,可以在构造函数上,标记一个特性,在选择构造函数的时候,就判断,如果标记有特性,就选择标记有特性的这个构造函数,如果没有特性,就选择默认最多的构造函数
//2.4.1 找标记的有特性的构造函数
ConstructorInfo ctor = contrArray.Where(a => a.IsDefined(typeof(SelectConstructorAttribute))).FirstOrDefault();
if (ctor == null)
{
ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
}
//3.准备构造函数的参数
List<object> prarlist = new List<object>();
foreach (ParameterInfo para in ctor.GetParameters())
{
Type paraType = para.ParameterType;
string shortName = GetKey(paraType,null);
Type parTargetType = richarDictionary[shortName];
//var target = Activator.CreateInstance(parTargetType);
var target = this.GetService(parTargetType);//递归
prarlist.Add(target);
}
#endregion
#region 对象注入
//构造对象
object oInstance = Activator.CreateInstance(type, prarlist.ToArray());
#endregion
#region 属性注入
//找出对象中需要做注入的属性,然后实例化属性对应类型的对象,赋值给属性
//1.找出所有的属性;
//2.找出指定标识,按照有标识的去给属性做注入 标识--Attribute
//拿出所有属性,使用特性判断需要注入的属性
var attArray = type.GetProperties().Where(p=>p.IsDefined(typeof(PropertyInjeictionAttribute),true));
foreach (var prop in attArray)
{
//找到具体类型
Type propType = prop.PropertyType;
string shortName = GetKey(propType,null);
Type propImType = richarDictionary[shortName];
//构造
object propInstance = GetService(propImType);
prop.SetValue(oInstance,propInstance);
}
#endregion
#region 方法注入
foreach (var method in type.GetMethods().Where(p => p.IsDefined(typeof(MethodImplAttributes), true)))
{
List<object> paraInjectionList = new List<object>();
foreach (var para in method.GetParameters())
{
Type paraType = para.ParameterType;
string shortName = GetKey(paraType, null);
Type paraImType = richarDictionary[shortName];
object mInstance = GetService(paraImType);
paraInjectionList.Add(mInstance);
}
method.Invoke(oInstance,paraInjectionList.ToArray());
}
#endregion
//多实现注入--一个接口多实现后,希望在都注册以后,能够选择性的获取具体的实例
//1.当然要标识一些,一个接口的两个实现在注册时候必然要标识下
//2.在获取的时候,就可以通过这个标识来构造对应的对象
//3.起个别名
return oInstance;
}
}
MethodInjeictionAttribute
[AttributeUsage(AttributeTargets.Method)]
public class MethodInjeictionAttribute:Attribute
{
}
PropertyInjeictionAttribute
[AttributeUsage(AttributeTargets.Property)]
public class PropertyInjeictionAttribute:Attribute
{
}
SelectConstructorAttribute
[AttributeUsage(AttributeTargets.Constructor)]
public class SelectConstructorAttribute:Attribute
{
}
核心代码
// 01 创建字典 就是用来保存实现和抽象的映射关系,就相当于配置文件里的相关信息
private Dictionary<string, Type> richarDictionary = new Dictionary<string, Type>();
// 02 将对象与细节储存在字典内 注册服务--其实就是把抽象和具体的映射关系保存起来
public void AddTransient(Type serviceType, Type implementationType,string shortName)
{
//使用别名完成多实现功能
string Key = GetKey(serviceType,shortName);
richarDictionary.Add(Key, implementationType);
//将对象与细节储存在字典内
//richarDictionary.Add(serviceType.FullName,implementationType);
}
//03 通过字典取出具体类型,使用反射创建实例
public T GetService<T>(string shortName)
{
//通过字典取出具体的类型
Type type = richarDictionary[typeof(T).FullName];
//通过反射创建实例
return (T)Activator.CreateInstance(type);
}
//04 使用递归完成多层级
Type type = richarDictionary[GetKey(typeof(T), shortName)];
return (T)this.GetService(type);