02 依赖倒置与控制反转
课程回顾
什么是AOP
AOP与OOP的关系和区别
静态代理(装饰器模式、代理模式)实现AOP
装饰器模式与代理模式的区别
课程目标
动态代理实现AOP
依赖倒置原则
控制反转
Unity基本使用
动态代理实现AOP
动态代理实现AOP的方式有为两种,一种是通过代码织入的方式。
PostSharp 可以实现AOP静态织入。
静态织入就是直接修改IL中间语言,它是对编译器的一种扩展,在编译的时候,直接把我们要添加的功能,编译进IL中。
另一种是通过反射来完成,这个我们不需要自己去做,已经有一些实现AOP的框架,比如Romoting(分布式通信框架)、Castle(从数据访问框架ORM到IOC容器,再到WEB层的MVC框架、AOP,基本包括了整个开发过程中的所有东西)、Unity
Romoting
服务类与上例相同,创建动态代理类
public class DynamicProxy<T> : RealProxy{ private readonly T _target; // 执行之前 public Action BeforeAction { get; set; } // 执行之后 public Action AfterAction { get; set; } // 被代理泛型类 public DynamicProxy(T target) : base(typeof(T)) { _target = target; } // 代理类调用方法 public override IMessage Invoke(IMessage msg) { var reqMsg = msg as IMethodCallMessage; var target = _target as MarshalByRefObject; BeforeAction(); var result = RemotingServices.ExecuteMessage(target, reqMsg); AfterAction(); return result; }}创建代理工厂类
public class ProxyFactory{ public static T Create<T>(Action before, Action after) { // 实例化被代理泛型对象 T instance = Activator.CreateInstance<T>(); // 实例化动态代理 var proxy = new DynamicProxy<T>(instance) {BeforeAction = before, AfterAction = after}; // 返回透明代理对象 return (T)proxy.GetTransparentProxy(); }}调用执行
static void Main(string[] args){ var acount = ProxyFactory.Create<AccountService>(() => { Console.WriteLine("注册之前"); }, () => { Console.WriteLine("注册之后"); }); public class User { public string Name { get; set; } public string Password { get; set; } } acount.Reg(user);}
Unity
使用 NuGet 安装 Unity 和 Unity.Interception 包
Install-Package UnityInstall-Package Unity.Interception创建拦截器类
public class AccountInterceptor : IInterceptionBehavior{ /// /// Invoke方法接受2个参数: /// input包含了调用的客户对象的方法名称和参数值的信息 /// getNext是一个委托,定义了拦截管道(或者代理对象)中要调用的下一个拦截行为。 /// 最终,它将调用结果返回给管道中的前一个拦截行为。 /// /// /// /// public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { User user = input.Inputs[0] as User; if (user.Password.Length < 10) { return input.CreateExceptionMethodReturn(new Exception("密码长度不能小于10位")); } Console.WriteLine("参数检测无误"); var result = getNext()(input, getNext); Console.WriteLine("之后"); return result; } /// /// GetRequiredInterface方法允许我们指定关联到该拦截行为的接口类型。 /// 在示例中,我们将在拦截器注册时指定接口类型,因此这个方法返回Type.EmptyTypes。 /// /// public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } /// /// WillExecute属性允许我们通过定义该行为是否应该执行来优化行为链; /// 这个属性永远返回true,因此这个拦截行为将始终执行。 /// public bool WillExecute => true;}Main方法中注册服务和拦截器
IUnityContainer container = new UnityContainer();container.AddNewExtension<Interception>();// RegisterType方法的第一个参数——Interceptor对象,定义了拦截器的类型,// 另外一个参数则注册了AccountInterceptor拦截器。container.RegisterType<IAccountService, AccountService>( new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<AccountInterceptor>());然后获取实例
// account变量并非是AccountService类型的,而是一个动态创建、实现了IAccountService接口的代理对象// 这个代理对象包含了IAccountService接口定义的方法、属性和事件var account = container.Resolve<IAccountService>();account.Reg(user);
Castel
https://www.cnblogs.com/youring2/p/10962573.html
作业1
什么是AOP?要求:非开发人员也能听明白,可以举例说明。
传统三层架构
通常意义上的三层架构就是将整个应用划分为:
界面层(User Interface layer)
主要表示WEB方式,也可以表示成WINFORM方式,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
业务逻辑层(Business Logic Layer)
主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。
数据访问层(Data access layer)
主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务.

区分层次的目的是为了实现“高内聚低耦合”的思想。
三层架构的缺点
界面层依赖业务逻辑层,业务逻辑层依赖数据访问层,这种自上而下的层层依赖会导致级联的修改,如果底层发生了修改,则可能需要逐层进行修改。
传统的三层架构,由于自上而下的依赖,很难顺利的实现团队协同开发,比如负责业务层的人,在数据层没有编写完是无法开始工作的。
因为,上层的功能取决于底层功能的实现,这不利于团队开发,只有高内聚,没有低耦合。
依赖倒置原则
依赖倒置原则,英文缩写DIP,全称Dependence Inversion Principle。
定义:
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
抽象不应该依赖于实现,实现应该依赖于抽象。
上面的定义不难理解,主要包含两次意思:
高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。换言之,模块间的依赖是通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。“面向接口编程”思想正是这点的最好体现。
依赖倒置与三层
传统三层架构中,UI层依赖于BLL层,BLL层依赖于DAL层。
由于每一层都是依赖于下层的实现,这样当某一层的结构发生变化时,它的上层就不得不也要发生改变,比如我们DAL里面逻辑发生了变化,可能会导致BLL和UI层都随之发生变化,这种架构是非常荒谬的!
如果我们换一种设计思路,高层模块不直接依赖低层的实现,而是依赖于低层模块的抽象。

具体表现为我们增加一个IBLL层,里面定义业务逻辑的接口,UI层依赖于IBLL层,BLL层实现IBLL里面的接口,所有具体的业务逻辑则定义在BLL里面,这个时候如果我们BLL里面的逻辑发生变化,只要接口的行为不变,上层UI里面就不用去改变。
依赖倒置的目的
在传统三层里面,高层模块直接依赖低层模块的实现,当我们将高层模块依赖于底层模块的抽象时,就好像依赖“倒置”了。这就是依赖倒置的由来。
通过依赖倒置,可以使得架构更加稳定、更加灵活、更好应对需求变化。
依赖倒置原则就是基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。
以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
在三层架构里面增加一个接口层能实现依赖倒置,它的目的就是降低层与层之间的耦合,使得设计更加灵活。
在介绍依赖倒置具体如何使用之前,我们需要引入IoC相关的概念,我们先来看看它们之间的关系。
控制反转
IoC 即控制反转 (Inversion of Control),一种反转流、依赖和接口的方式(DIP的具体实现方式),它把传统上由程序代码直接操控的对象的调用权交给第三方,通过第三方来实现对象组件的装配和管理。
假设你是一个五岁的小孩,想要吃点东西,如果你自己从冰箱拿食物,你可能会让门开着,还可能会得到父母不想让你拥有的东西,甚至可能在寻找一些我们甚至没有或者已经过期的东西。
你应该做的是陈述一下需求,比如"我饿了,想要吃XX",你的父母就会做好你需要的食物,然后拿给你。
从你想吃东西的需求,主动去冰箱拿(正转)食物,到你想要吃的食物而被动获得(反转),前者你主动从冰箱获得,而后者由你的父母,也就是第三方把食物给你,而你并不关心食物从哪来,以及怎么得来。
所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
通俗地说,就是应用程序本身不负责依赖对象的创建和维护,而是将它交给第三方来负责,这样控制权就由应用程序转移到了第三方,即控制权实现了所谓的反转。
例如在类型A中需要使用类型B的实例,而B 实例的创建并不由A 来负责,而是通过第三方来创建。
依赖注入(DI)
依赖注入即DI(Dependency Injection),是IoC的一种实现方式,用来反转依赖。
场景
假设你是一个五岁的小孩,想要吃点东西,如果你自己从冰箱拿食物,你可能会让门开着,还可能会得到父母不想让你拥有的东西,甚至可能在寻找一些我们甚至没有或者已经过期的东西。
你应该做的是陈述一下需求,比如"我饿了,想要吃XX",你的父母就会做好你需要的食物(依赖),然后拿给你(注入)。
依赖注入不是目的,它是一系列工具和手段,最终的目的是帮助我们开发出松散耦合、可维护、可测试的代码和程序。
在调用一个类的时候,先要实例化这个类,生成一个对象。如果你在写一个类,过程中要调用到很多其它类,甚至这里的其它类,也要“依赖”于更多其它的类,那么可以想象,你要进行多少次实例化,依赖关系有多么复杂。
如果面试官问你,为什么要依赖注入,你可以一句话回答他,因为我们要实现控制反转。
如果面试官继续问,为什么要控制反转,因为我们的软件设计需要符合依赖倒置原则。
IoC容器
IoC 即控制反转 (Inversion of Control),一种反转流、依赖和接口的方式(DIP的具体实现方式),它把传统上由程序代码直接操控的对象的调用权交给第三方,通过第三方来实现对象组件的装配和管理。
这里的第三方,有一个专用名词叫IoC容器。
IoC容器是由依赖注入框架提供的,它是依赖注入框架的主心骨,它主要用来映射依赖,管理对象的创建和生存周期。
假设你是一个五岁的小孩,想要吃点东西,如果你自己从冰箱拿食物,你可能会让门开着,还可能会得到父母不想让你拥有的东西,甚至可能在寻找一些我们甚至没有或者已经过期的东西。
你应该做的是陈述一下需求,比如"我饿了,想要吃XX",你的父母(IoC容器)就会做好你需要的食物(依赖),然后拿给你(注入)。
IoC容器本身也是个对象,你把某个类(不管有多少依赖关系)放入这个容器中,它都可以“解析”出这个类的实例,并且还可以把这个类依赖的对象,也实例化出来,然后注入进去。
所谓的依赖注入就是把有依赖关系的类放入IoC容器中,然后解析出这个类的实例。
实现IoC容器的框架有很多,.Net中常用的有:Unity、Autofac、Ninject。
案例
场景是这样的,父亲给孩子讲故事,只要给她一本书,他就可以照着书给孩子讲故事了。
传统方式
不经过任何设计模式和设计原则加工的传统OOP方式。
public class Book{ public string GetContent() { return "从前有座山,山上有座庙....."; }}public class Father{ public void Read() { Book book = new Book(); Console.WriteLine("爸爸开始给孩子讲故事了"); Console.WriteLine(book.GetContent()); }}public class Program{ public static void Main() { Father father = new Father(); father.Read(); }}从关系图上可以很明显的看出来,Father类直接依赖Book

但是如果有一天需求改变了:不是给书,而是给了一份报纸,让这位父亲读一下报纸上的新闻。
public class Paper
{
public string GetContent()
{
return "王聪聪被限制高消费......";
}
}如果还是使用之前的Father类会发现,爸爸这时不会读报纸上的新闻。只是将书换成了报纸,我们就要去修改Father类的代码才能实现。
假如以后报纸换成杂志呢?换成网页呢?还要不断地修改Father类,这显然不是好的设计。原因就是Father与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。
工厂模式
工厂模式是我们最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
所以这里可以引入一个抽象的接口,IReader——读物,只要是可以阅读的都属于读物。
public interface IReader
{
string GetContent();
}创建一个读物工厂类,以后有其它读物,就可以在工厂方法中添加:
public static class ReaderFactory
{
public static IReader GetReader(string readerType)
{
if(string.IsNullOrEmpty(readerType)){
return null;
}
switch (readerType)
{
case "Paper":
return new Paper();
case "Book":
return new Book();
default:
return null;
}
}
}修改Father类:
public class Father
{
public IReader Reader { get; set; }
public Father(string readerName)
{
Reader = ReaderFactory.GetReader(readerName);
}
public void Read()
{
Console.WriteLine("爸爸开始给孩子讲故事了");
Console.WriteLine(Reader.GetContent());
}
}Father类与接口IReader发生依赖关系,而Book和Paper都属于读物,它们各自都去实现IReader接口,这样就符合依赖倒置原则了。

从关系图中,可以看出来,Father类不再依赖Book类了,而是依赖工厂类和接口,这里就符合了依赖导致原则:
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
抽象不应该依赖于实现,实现应该依赖于抽象。
我们可以把 IoC容器 看做是工厂模式的升华,把 IoC容器 看做是一个带有配送物流的超级工厂。
依赖注入
为什么说IoC容器是带有配送物流的超级工厂呢,因为IoC容器不仅能够创建对象,还可以把对象送到需求方的手里,也就是注入进使用方中。
依赖注入常见的方式有两种:构造函数注入和属性注入。
修改 Father 类
public class Father
{
public IReader Reader { get; set; }
public Father(IReader reader)
{
Reader = reader;
}
public void Read()
{
Console.WriteLine("爸爸开始给孩子讲故事了");
Console.WriteLine(Reader.GetContent());
}
}从关系图中,可以发现,Father类只依赖接口,连工厂类也不再依赖。

Unity基本使用
Unity容器是一款功能齐全的通用IoC容器,可用于任何类型的.NET应用程序。它是开源的,并在Apache 2.0许可下发布。
Unity易于扩展,可扩展性强。任何人都可以编写可更改容器行为或添加新功能的扩展。例如,Unity提供的AOP拦截功能就属于Unity的容器扩展。
static void Main(string[] args)
{
// 创建容器对象
var container = new UnityContainer();
// 在容器里注册 接口与实现类,创建依赖关系
container.RegisterType();
// 在容器里注册Father类
container.RegisterType();
// 从容器里拿出要使用的类,容器会自行创建Father对象
// 并且会从构造函数注入Father所依赖的对象
var father = container.Resolve();
father.Read();
}
关注"软谋dotnet"公众号
本文介绍了依赖倒置原则(DIP)及其在传统三层架构中的问题,强调了高层模块应依赖抽象而非具体实现。接着探讨了控制反转(IoC)和依赖注入(DI)的概念,以及它们如何降低模块间的耦合。此外,文章还详细讲解了动态代理在AOP中的应用,并以Unity框架为例展示了如何实现IoC和AOP。最后,通过代码示例展示了Unity容器的使用方法和依赖注入的实现。
2万+

被折叠的 条评论
为什么被折叠?



