六、反射、特性、依赖注入
—— 反射大多数时候和接口、依赖反转原则配合使用 —— 因此本章需要配合前文<接口>来理解。
—— 特性和依赖注入都是基于.Net的反射机制,三者关系密切。
6.1 关于反射
反射,Reflect,不是C#语言的功能,是.NET框架提供的功能。
它的地位非常重要,是java、C#等托管类型语言 和 C、C++等原生语言相比,最大的区别之一。
反射应用的初衷:程序的逻辑充满不确定(很多时候要等到用户交互时才能确定),如果想要在静态编译状态下预测出各种结果,就需要大量的逻辑判断,程序会变得臃肿。而且更重要的一点是:往往很难穷尽各种可能 —— 这时就需要程序有一个“以不变应万变”的方法。
反射可以实现:给它一个对象,它可以在不用new操作符、也不知道对象具体是什么静态类型的情况下,创建出一个同类型的对象 —— 进一步解耦
因此,在我们实际编程过程中,很少直接接触反射,大部分时候使用的都是封装好的反射。(单元测试、依赖注入、泛型编程都是基于反射机制。)
6.2 应用案例1 : 直接使用反射
注意:
- .NET Framework & NET Core都有反射机制,但是二者调用的类库不一样。(下面用.NET Core来做案例)
- 程序中不要盲目使用反射机制,以免影响程序性能。
6.3 应用案例2 :通过依赖注入DI使用反射
DI和DIP的关系:DIP是个设计概念,DI是在这个概念的基础上,结合接口、反射机制所形成的应用。没有DIP就没有DI。
依赖注入要借助依赖注入框架:
代码中引入命名空间:
使用之前需要了解——
依赖注入中最重要的一个概念:container。也称ServiceProvider / ServiceCollection
原理是:把各种类型和对应的接口放入容器,之后就可以直接向容器请求对象(可以设置请求对象的模式:瞬态、范围、单例)
(1)依赖注入的基本用法
结合以上代码可知,使用 依赖注入的好处:
假如业务需求更新,原本使用的HeavyTank类需要变更为MiddleTank。
如果是按照传统的new方式实例化对象,那程序中所有new的地方都需要修改。有多少个new就要修改多少次。
而使用依赖注入的方式,只需要修改一行代码 —— 变更放入容器中的类型即可:
sc.AddScoped(typeof(ITank), typeof(LightTank));
(2)进阶用法
**如以上代码,当想要创建一个Driver实例——
传统流程:首先创建一个驾驶的IVehicle类型的对象Car,将对象传给Driver的构造器……
依赖注入流程:直接创建Driver实例。这时系统会自动去找一个IVehicle类型的实例(因为driver和Ivehicle都放置到容器中了),容器里IVehicle注册的对象是Car,所以系统会创建一个Car的对象给Driver—— 这部分的工作都由系统来完成。实际写的代码只要一行即可。
Driver driver = sp.GetService<Driver>();
同理,如果需要把Car改为其他类,也只需要修改一行代码即可:
sc.AddScoped(typeof(IVehicle), typeof(Bicycle));
注入体现在:用注册好的类型创建的实例注入到Driver的构造器。
6.4 应用案例3:反射的另一用途——松耦合
反射的第一个用途:依赖注入
反射的第二个用途:松耦合
松耦合主要体现在插件式编程。
插件式编程:不与主题程序一起编译,但可以与主体程序一起工作的组件。往往由第三方提供。
插件式编程的好处:以主题程序为中心,生成生态圈。主体——》插件
—— 主体和插件的关系,就是反射的以不变应万变的关系。
一般情况下,主题程序都包含有api,sdk,第三方可以借助这些来更便捷、更规范的进行开发,方便与主程序对接。
案例:
假如我是一个婴儿车开发厂商,新开发了一款支持识别动物功能的婴儿车——
婴儿点击数字面板上的小动物头像,再点击开发次数,音响就会发出小动物的叫声。
婴儿车出厂时只默认提供几种小动物的配置;为了提高产品竞争力,我在车上留了一个USB插口,允许其他厂商通过U盘来实现动物种类的扩充。
- 这里的主体程序:婴儿车识别动物功能
- 组件程序:动物类库插件
主体程序根目录下存在Animals文件夹,存放其他厂商开发的小动物插件。
主题程序会读取文件夹,加载第三方插件,调用第三方所有小动物类的Voice方法。
USB接口规定:插件中的小动物类要包含一个Voice方法,且要可以接受一个int类型的参数,用来判断小动物叫几次。
(1)纯反射模式开发
主体程序:ReflectDemoApp1
组件程序:Animals.Lib & Animals.Lib2
组件程序完成后,编译,到根目录复制两个 dll文件,放入主体程序的Animals文件夹 —— 即完成USB插U盘的步骤
之后正常运行主体程序——
运行结果:
到这里我们可以发现,使用纯反射有一些缺点:
比如插件开发商写错了动物类中的方法名——主体调用时会直接过滤掉这个类。
再比如,调用……
——为了避免如上这些不必要的错误、降低开发成本,第一方往往会发布SDK,帮助第三方快速开发。
(2)SDK模式开发
SDK需要考虑到的功能:
- 统一规范:准备一个IAnimal接口,封装Voice方法,所有开发动物类的第三方都要实现这个接口——主体程序使用时,可以直接将对象转化为IAnimal类型的对象,就不需再用弱类型(method.Invoke)方式来调用Voice方法。
- 考虑到第三方的组件中有时可能包含未开发完成的动物类,主体需要加一个artibute类型,第三方把attribute attach到未开发完的动物类,主体会自动过滤调这部分
SDK:
SDK在主体外,是一个类库,以dll的形式给到第三方。第三方开发插件时引用dll即可。
PS.主体程序也需要包含此SDK
组件更新:
组件改动后,要把新版本的组件 dll 加入到主体程序
主体程序更新:
运行结果:
ps.案例源码: