每个软件,都是由很多“组件”构成的。这里的“组件”是指广义的组件 —— 组成部件,它可能是函数,可能是类,可能是包,也可能是微服务。软件的架构,就是组件以及组件之间的关系。而这些组件之间的关系,就是(广义的)依赖关系。
软件的维护工作,本质上都是由“变化”引起的,只要软件还活着,我们就无法对抗变化,只能顺应它。而组件之间的依赖关系决定了变化的传导范围。
一般来说,当被依赖的组件变化时,其依赖者也会随之变化。软件开发最怕的就是牵一发而动全身。所幸,并不是每次变化都必然会传导给它的依赖者们。
对于具体实现细节的修改,只要没有改变其外部契约(可简单理解为接口),其依赖者就不需要修改。对于更大规模的修改,比如更换计费策略,我们是不是就无法控制其传播了?也不见得。只要我们的设计能让两者的接口保持一致,就可以把变化控制在尽可能小的范围内。
对于减少变更传播的方式,Bob 大叔在《架构整洁之道》中做过系统性的阐述,如下图:
在这张图中,我们要注意箭头的指向,箭头的指向就是依赖的方向。我们看到,所有的依赖都指向了用例(领域服务)并最终指向了业务实体。这样的架构很理想,因为业务实体的变化通常都比较少,即使变,通常也是必要的、无法回避的变化。反之,支持新的用户界面、换数据库、换外部系统等方面的变化都只会在很小的范围内传播。回顾一下你维护过的系统的各种变更,都属于哪一类呢?
这张图看起来很美,却和我们传统的编程方式截然不同。以展示器为例,在传统的编程方式下,我们需要在展示器中创建用户界面类的实例,并且设置用户界面的值以及接收来自用户界面的事件。这时候,我们就不得不让展示器“引用”用户界面的类,这样就产生了一个从展示器指向用户界面的依赖,破坏了架构的整洁性。
所以,我们要找到这样一种方式,让展示器能“控制”界面类。更泛化的说法是,让位于架构内层的组件,“控制”位于架构外层的组件,“控制”的方向与“依赖”的方向相反,所以,我们称其为“控制反转”(IoC)。
要想解除展示器对界面类的这种错误依赖,我们可以从界面类抽象出一个接口,展示器定义这样一个接口,然后让界面类实现它,这样展示器就不需要再知道界面类的存在了。唯一的问题是:接口本身是抽象的,所以它不能被直接实例化。如果让展示器负责实例化界面类,那么展示器又必须引用界面类了。好吧,一切似乎又回到了原点……不过,我们有另外的办法打破这种僵局。
引入第三方,通常是打破僵局的最佳方式之一。我们需要这样一个第三方:
这种“衣来伸手,饭来张口”的模式就叫做依赖注入,简称 DI。它是实现 IoC 最常见的方式。这个大管家,有个人尽皆知的名片,上面写着几个大字:Injector(注入器),当然也有人写得啰嗦一点:DI Container(注入容器)。
依赖注入的工作逻辑是这样的:
所以,注入器是这个依赖注入体系中的核心角色,它通过三个内部数据结构:提供者映射表、依赖树、实例映射表来完成整个过程。
但是,往“提供者映射表”中注册的过程该由谁来做呢?如果由界面类或展示器类自己做,那么它们将不得不“引用”注入器,而这不符合注入器的“人设”,理想的注入器应该是默默无闻的,最好让谁都不需要知道它。
那就只能让注入器自行完成这项工作了。
问题在于,注入器要怎么知道哪些类是可注入的呢?所以,我们要让这些可注入的类在实例化之前就具有某些特征,比如静态属性、静态方法,或者注解(Annotation)。
注解是最常见的方式,它像注释一样不被当做代码来执行,而是专门供别人阅读。但注释的读者完全是人类,而注解的主要读者除了人类之外还有框架或预编译器。
启动的时候,注入器会搜集具有指定特征的类,并将其注册到提供者映射表中,并且查找这些类的构造函数或具有某些特征的属性等,分析出其依赖关系,构建出“依赖树”。
在前面的例子中,为了便于理解,我一直用类作为依赖提供者。但实际上,依赖提供者可以有很多形态:常量、变量、函数或者类都是可以的。
我们对依赖提供者唯一的期望是:它能给我们提供一个与特定标记对应的实例。至于这个实例是 new 出来的还是调用出来的,我们根本不需要关心。
通过这种面向目标而非面向实现的抽象化设计,我们为应对未来的变化留出了大量的灵活性,却不用付出多少代价。
控制反转在整洁架构中非常重要,而大部分现代框架的设计思想都是暗合《架构整洁之道》的,因此,在大部分现代框架中,都内置了依赖注入机制,以支持控制反转。
最经典、最著名的当属 Spring Framework,可以说依赖注入无处不在。在 Java 后端开发中它几乎一统江湖就是其成功的见证。
在 Android Framework 等移动端框架中,依赖注入也是最常见的框架功能之一。
在前端领域,Angular 从 2009 年的 AngularJS 0.x 开始就支持了依赖注入。到了 Angular 2+ 的时代,借助 TypeScript 的语言特性,它甚至做得比 Spring Framework 还要全面、深入。
在面向对象编程的经典著作《设计模式》中,策略模式是使用最广泛的模式之一。但自行实现策略模式很繁琐,也容易出错。而依赖注入是策略模式的最佳实现方式之一。如果我们在设计中使用了策略模式,那么所有繁琐的工作都可以交给依赖注入框架来完成。我们抽象好策略接口,等将来需要换个策略时,只要修改下配置就可以了,而消费方的代码一点都不需要改。
深入理解了依赖注入技术,就可以将其灵活运用在你的各项实际工作中,设计出优美而灵活的应用架构。未来的你,以及接手你工作成果的人,会从你的高质量设计中受益匪浅。
南瓜粥,南瓜饼、南瓜水饺、焗南瓜,各种各样用南瓜做的美食,三天两头轮换着吃。那么熬南瓜粥什么时候放南瓜?南瓜粥南瓜要去皮吗?南瓜粥先放南瓜还是先放米建议先放米。建议在做南瓜粥的时候先放米再放南瓜,一方面是因为米比南瓜更难熟,如果米后面才放的话,吃起来可能会出现夹生的情况,并且南瓜中含有较多的维生素C
主料,茄子粉条。配料.香葱切段调料. 耗油,面酱,烧烤酱,番茄酱各适量。口味,酱香味浓操作方法1,茄子改一下刀切条型。上锅煎至变色即可,不要过于太瘦。煎好备用。2,先把粉条加适量老抽上色炒出备用,加适量油避免粘连。3,下调料炒出香味,加适量水,放入茄子煮熟入味。加糖适量提鲜。4,汤汁浓稠时加粉条香葱
白蛤学名为四角蛤蜊,是我国沿海常见的底栖经济贝类,肉细味美、营养丰富。一般主要作为做汤的材料。未到特别鲜美!鲜活的白蛤刚出滩的鲜活白蛤白蛤的采捕季节应在春冬、初夏或秋季。采捕方法可用脚踩或耙子耙,贝苗受踩受耙露出滩面而拾之。运输日期应根据当时气温高低,在耐干范围内运到,运输中最理想的是用筐或草包装运
【制作主料】:鸡腿2个、莴笋一根、洋葱半个、香菜1棵【煮鸡腿调料】:姜几片、葱2根根、料酒10克【料汁调料】:鲜红辣椒1个、大蒜2瓣、熟芝麻10克、盐2克、白糖3克、香醋5克、生抽5克、植物油10克、辣椒油10克——制作方法和步骤——【步骤一】:把鸡腿洗干净然后凉水下锅,然后依次放入姜几片、葱2根根
前段时间熬了一些猪油,一直想做个酥皮点心,刚好家里还有很多红豆,索性就做个红豆酥饼吧。做酥皮点心的话用猪油的酥脆效果会更好一些,不过猪油不是家里常备的材料,没有的话用普通的食用油也是可以的哦,照样很酥脆。里面的馅料完全可以根据自己的喜好来调换,绿豆、榴莲、糖馅儿的都可以。这块酥皮点心家里没有烤箱的话
1、开味小菜,又称开胃小菜、开胃菜,食用时间通常是主菜上菜前或连同主菜一起食用。2、 今天就来分享一下“开胃小菜”做法,喜欢的朋友可以先收藏,有空自己试一下。 3、下面开始介绍所需要的食材:小青菜、香菜、小米辣、蒜子 4、先把香菜切末,放入盆中,菜白竖着切片,再切小段切的尽量细一点,同样放入盆中,小