认识元编程、控制反转(IoC)以及依赖注入(DI)

认识元编程、控制反转(IoC)以及依赖注入(DI)

Tags
ES6
IoC
DI
面向对象编程
前端工程化
元编程
CreatedTime
Aug 21, 2022 02:39 PM
Slug
2020-10-19-metadata-ioc-di
UpdatedTime
Last updated August 21, 2022

元编程

定义

狭义来说,应该是指「编写能改变语言语法特性或者运行时特性的程序」。换言之,一种语言本来做不到的事情,通过你编程来修改它,使得它可以做到了,这就是元编程。

在 ES6 中的体现

从这个角度来看,Proxy、Reflect是js的元编程。并且装饰器中,为类方法的参数设置要求(类型、是否必须)等都是元编程,因为js本身或者ts本身(只能声明类型、不能校验或者转化)都做不到,通过编码做到了。
Proxy:可以改变对象的默认行为。例如可以拦截对象的读、写等操作。
Reflect:反射,可以获取元信息。配合 reflect-metadata,更加强壮。例如可以在对象未实例化时,在对象外部,获取构造函数参数类型、方法参数类型、属性类型。

更多资料

怎么理解元编程?
Meta- 这个前缀在希腊语中的本意是「在...后,越过...的」,类似于拉丁语的 post-,比如 metaphysics 就是「在物理学之后」,这个词最开始指一些亚里士多德的著作,因为它们通常排序在《物理学》之后。 但西方哲学界在几千年中渐渐赋予该词缀一种全新的意义:关于某事自身的某事。比如 meta-knowledge 就是「关于知识本身的知识」,meta-data 就是「关于数据的数据」,meta-language 就是「关于语言的语言」,而 meta-programming 也是由此而来,是「关于编程的编程」。 弄清了词源和字面意思,可知大陆将 meta- 这个前缀译为「元」并不恰当。台湾译为「后设」,稍微好一点点,但仍旧无法望文生义。也许「自相关」是个不错的选择,「自相关数据」、「自相关语言」、「自相关编程」--但是好像又太罗嗦了。 Anyway。先看看 meta-data: 「我的电话是 +86 123 4567 8910」 --这是一条数据; 「+86 123 4567 8910 有十三个数字和一个字符,前两位是国家代码,后面是一个移动电话号码」 -- 这是关于前面那条数据的数据。 那么照猫画虎,怎样才算 meta-programming 呢?泛泛来说,只要是与编程相关的编程就算是 meta-programming 了--比如,若编程甲可以输出 A - Z,那么写程序甲算「编程」;而程序乙可以 生成 程序甲(也许还会连带着运行它输出 A - Z),那么编写程序乙的活动,就可以算作 meta-programming,「元编程」。注意,程序甲和程序乙并不一定是同一种语言: 如此说来,inline SQL 甚至动态生成 HTML 也是元编程了?抠定义的话是这样吧。 不过
怎么理解元编程?
JavaScript【重温基础】14.元编程
本文是 重温基础 系列文章的第十四篇。 这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀 接下来会统一整理到我的 【Cute-JavaScript】的 JavaScript基础系列 中。 今日感受:独乐乐不如众乐乐。 本章节复习的是JS中的元编程,涉及的更多的是ES6的新特性。 元编程,其实我是这么理解的: 让代码自动写代码,可以更改源码底层的功能。 元,是指程序本身。 有理解不到位,还请指点,具体详细的介绍,可以查看 维基百科 元编程 。 从ES6开始,JavaScrip添加了对 Proxy和 Reflect 对象的支持,允许我们连接并定义基本语言操作的自定义行为(如属性查找,赋值,枚举和函数调用等),从而实现JavaScrip的元级别编程。 Reflect: 用于替换直接调用Object的方法,并不是一个函数对象,也没有constructor方法,所以不能用new操作符。 Proxy: 用于自定义对象的行为,如修改set和get方法,可以说是ES5中Object.defineProperty()方法的ES6升级版。 两者联系: API完全一致,但Reflect一般在Proxy需要处理默认行为的时候使用。 参考资料 : 本文主要从Proxy介绍,还会有几个案例,实际看下怎么使用。 proxy 用于修改某些操作的 默认行为,可以理解为一种拦截外界对目标对象访问的一种机制,从而对外界的访问进行过滤和修改,即代理某些操作,也称" 代理器 "。 基本语法: proxy实例化需要传入两个参数, target参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为。 上述 a实例中,在第二个参数中定义了 get方法,来拦截外界访问,并且 get方法接收两个参数,分别是 目标对象和 所要访问的属性,所以不管外部访问对象中任何属性都会执行 get方法返回 leo 。 同个拦截器函数,设置多个拦截操作
JavaScript【重温基础】14.元编程
第七章:元编程 · 你不懂JS:ES6与未来 · 看云
第七章:元编程 元编程是针对程序本身的行为进行操作的编程。换句话说,它是为你程序的编程而进行的编程。是的,很拗口,对吧? 例如,如果你为了调查对象`a`和另一个对象`b`之间的关系 -- 它们是被`[[Prototype]]`链接的吗? -- 而使用`a.isPrototypeOf(b)`,这通常称为自省,就是一种形式的元编程。宏(JS中还没有) -- 代码在编译时修改自己 -- 是元编程的另一个明显的例子。使用`for..in`循环枚举一个对象的键,或者检查一个对象是否是一个"类构造器"的 *实例*,是另一些常见的元编程任务。 元编程关注以下的一点或几点:代码检视自己,代码修改自己,或者代码修改默认的语言行为而使其他代码受影响。 元编程的目标是利用语言自身的内在能力使你其他部分的代码更具描述性,表现力,和/或灵活性。由于元编程的 *元* 的性质,要给它一个更精确的定义有些困难。理解元编程的最佳方法是通过代码来观察它。 ES6在JS已经拥有的东西上,增加了几种新的元编程形式/特性。 ## 函数名 有一些情况,你的代码想要检视自己并询问某个函数的名称是什么。如果你询问一个函数的名称,答案会有些令人诧异地模糊。考虑如下代码: ```source-js function daz() { // .. } var obj = { foo: function() { // .. }, bar: function baz() { // .. }, bam: daz, zim() { // ..

控制反转(IoC)

控制反转是面向对象编程中的一种原则,用于降低代码之间的耦合度。
传统方法:在类的内部主动创建依赖对象,这样将导致类与类之间耦合度非常高,并且不容易测试。
import { ModuleA } from './module-A'; import { ModuleB } from './module-B'; class ModuleC { constructor() { this.a = new ModuleA(); this.b = new ModuleB(this.a); } } @Injectable() class ModuleC { constructor( private a: ModuleA, private b: ModuleB ) {} }
控制反转:将创建和查找依赖对象的控制权交给了IoC容器,这样对象与对象之间就是松散耦合了,方便测试与功能复用
// 文件1:container.js import { ModuleA } from './module-A'; import { ModuleB } from './module-B'; // 将模块统一注入到IoC容器中 export const iocContainer = new Container(); container.bindModule(ModuleA); container.bindModule(ModuleB); // 文件2: ModuleC文件 import { container } from './container'; class ModuleC { constructor() { this.a = container.getModule('ModuleA'); this.b = container.getModule('ModuleB'); } }
此时,对于ModuleC来说,可以对接不同的容器,而不同容器中的ModuleA和ModuleB各不相同,相比于传统方法,可以做到在不改动ModuleC的情况下,实现不同类的注入。
同理,对于测试来说,可以mock ModuleA和mock ModuleB,将测试的点聚焦于ModuleC本身的逻辑。

依赖注入(DI)

依赖注入是控制反转最常见的一种应用方式(或者实现方法),即通过控制反转,在对象创建的时候,自动注入一些依赖对象。
在TS中,使用装饰器和元编程,可以实现依赖注入。看看NestJS中DI的写法:
import { Injectable } from '@nestjs/common'; import { TcbService } from './../../services/tcb.service'; // 通过装饰器@Injectable()让依赖(TcbService)注入到类实例中 @Injectable({ scope: Scope.DEFAULT }) export class SearchService { constructor(private readonly tcbService: TcbService) { } public async searchPassages(): Promise<> { const db = this.tcbService.getDB(); // 可以访问被注入的依赖 // ...... } }

参考链接

基于 TypeScript 的 IoC 与 DI
在使用 Angular或者 Nestjs 时,你可能会遇到下面这种形式的代码: 上述代码中使用了 Component的装饰器,并在模块的 providers中注入了需要使用的服务。这个时候,在 AppComponent中 otherService将会自动获取到 OtherService 实例。 你可能会比较好奇, Angular是如何实现这种神奇操作的呢?实现的过程简而言之,就是 Angular在底层使用了IoC设计模式,并利用 TypeScript 强大的装饰器特性,完成了依赖注入。下面我会详细介绍IoC与DI,以及简单的DI实例。 理解了IoC与DI的原理,有助于我们更好的理解和使用Angular及Nestjs。 IoC 英文全称为 Inversion of Control,即 控制反转 。控制反转是面向对象编程中的一种原则,用于降低代码之间的耦合度。传统应用程序都是在类的内部主动创建依赖对象,这样将导致类与类之间耦合度非常高,并且不容易测试。有了 IoC 容器之后,可以将创建和查找依赖对象的控制权交给了容器,这样对象与对象之间就是松散耦合了,方便测试与功能复用,整个程序的架构体系也会变得非常灵活。 正常方式的引用模块是通过直接引用,就像下面这个例子一样: 这么做会造成 ModuleC依赖于 ModuleA和 ModuleB,产生了模块间的耦合。为了解决模块间的强耦合性, IoC 的概念就产生了。 我们通过使用一个容器来管理我们的模块,这样模块之间的耦合性就降低了(下面这个例子只是模仿 IoC 的过程,Container 需要另外实现): 为了让大家更清楚 IoC 的过程,我举一个例子,方便大家理解。 当我要找工作的时候,我会去网上搜索想要的工作岗位,然后去投递简历,这个过程叫做控制正转,也就是说控制权在我的手上。而对于控制反转,找工作的过程就变成了,我把简历上传到拉钩这样的第三方平台(容器),第三方平台负责管理很多人的简历。此时HR(其他模块)如果想要招人,就会按照条件在第三方平台查询到我,然后再联系安排面试。 DI 英文全称为 Dependency Injection,即 依赖注入 。依赖注入是控制反转最常见的一种应用方式,即通过控制反转,在对象创建的时候,自动注入一些依赖对象。 在 Nestjs或 Angular中,我们需要通过装饰器@Injectable()让我们依赖注入到类实例中。而理解他们如何实现依赖注入,我们需要先对
基于 TypeScript 的 IoC 与 DI