新手代码进阶之路 – 控制反转和依赖注入

本文章适合想提升代码通用性和扩展性的同学,帮助你以更宏观的视野去组织你的代码。以下的实例都为本人实际操作总结并已于生产环境使用,具有一定的指导性,可选择性借鉴。

控制反转和依赖注入

这俩思想主打的就是一个解耦。控制反转(Inversion of Control,IoC)是一种设计模式,它可以使得应用程序的组件更加松散耦合。顾名思义,本来我该安排自己的行程,但是我为了不麻烦,不想管这么宽,所以交给一个专门的人去做,可以看做是报个旅游团,我只负责吃喝玩,去哪,什么时候去都是导游的活。

把“我”看做是一个对象,我需要“买车票”,“订酒店”,“制定出行路线”,这些本该我自己控制的事情,我交给了导游去做,就是所谓的控制反转。

依赖注入,依赖注入(Dependency Injection,DI)是实现控制反转的一种技术手段。就是将对象A的依赖对象B,通过传入的方式来使用。这要求对象B的功能要满足需求,也就是对象B需要实现对应的接口。举个例子,旅行社需要导游才能开团,但是导游并不需要依赖具体的某个人,而是能胜任导游工作的任何人。导游的工作可以被看做是接口,会这些工作的人就能理解为实现这些接口的对象。我们将会导游工作的人注入到旅行社,那么旅行社开团的依赖就得到了满足,而导游是男是女,身高多少有什么爱好这些都可以根据我们的需求进行定制招聘(瞎说的,理解就行)。

实例讲解

以下为个人在实际敲代码的时候的一些例子,它们可能都有更好的解决方法,但我想通过这些例子来说明解耦的重要性和控制反转,依赖注入的直观映像。特别是当你觉得你的代码不够灵活,这个地方写了不能拿到其他地方用或者改动会很大的情况。

1. 指引教程

简介

我们在很多网站都能看到,当用户第一次进入该网站时,会出现一个新手指引的弹框,然后教你一步一步的使用这个网站。这类教程的底层逻辑都是差不多的,完成当前这一步,然后点击下一步或者监听某个动作完成,去到下一步。但是差异化的是每一个网站他们的步骤和跳转的逻辑都不一样。
新手代码进阶之路 - 控制反转和依赖注入

反模式

如果不考虑扩展性,一般我们会怎么做。这里有一个脑子里第一时间闪过的思路,一开始先展示step1,在点击下一步的时候再展示step2, …
这样做,控制跳转的逻辑放哪了,放在了step里,step直接控制。这样做的弊端是什么呢,首先我们需要手动渲染step,我们需要手动的进行下一步的跳转,每插入一个step我们的跳转逻辑可能都得跟着改。这样做唯一的好处就是高度控制,每一个step都可以完全定制,但这个好处对于教程来说又不太实用,因为教程step之间如果差异过大,会起到反作用。

优化

在上面的例子中,我们可以通过代码抽离,集中控制来优化它,但如果你都做到这一步了,那你离我们的目标控制反转也不远了。控制反转还有一个概念叫做==控制反转容器==,它就像一开始例子中我们讲的旅行社,它来控制“我”的旅游行程。所以优化的第一步,我们得先有一个容器。
新手代码进阶之路 - 控制反转和依赖注入
这个控制容器负责step的渲染,跳转逻辑。
第二步就是将所有的step传给这个容器就行了,step之间只需要定义好顺序或者指定step的下一个step,容器就能实现跳转逻辑next()。如果说要自定义step的内容,那step还可以传递一个renderer函数,再由控制器执行进行render。
到这里,我们的steps已经把逻辑抽离出来给到容器了,不管用在哪里,我们都只需要修改steps就好了,容器是通用的,易扩展的。

进一步

那么其实还有更复杂的情况,上面我们实现的逻辑,跳转完全由容器控制的,所以step并不能自由的进行跳转。这会导致我们的step很呆板,只能应对一个固定的跳转模式。而有些教程是需要监听用户的行为进行下一步跳转的,比如第一个是用户点击了某个按钮,第二步是用户输入了某个值。那么自动跳转的行为就不太好给到容器去控制了,注意我们这里说的是自动跳转的逻辑不适合给容器做,但是单纯跳转的逻辑还是可以的。

我们需要将容器的控制权转让一部分出去,也就是==控制反转再反转==!step把跳转的行为给到容器控制,容器再把跳转的权限给到step进行访问。这样就能实现step不用担心下一步跳到哪,该不该结束,要不要skip,这些都由容器控制。而容器也不用担心什么时候跳转呢,这个由step自己去控制。
新手代码进阶之路 - 控制反转和依赖注入

2. Logger

简介

为什么要选logger作为一个实例呢,因为它把依赖注入的思想用到恰到好处,什么是logger,用来干嘛的。简单的说,我们日常用的console就可以看做是一个logger,它会将日志打印到控制台。我们现在有一个renderer用来渲染传入的App,他需要一个logger,我们想要在本地开发的时候用console作为logger,在给用户preview App的时候用可视化debugger的logger,在App跑在生产环境的时候用远程监控系统的logger。简单的来说就是不同环境我们需要给这个renderer不同的logger。
新手代码进阶之路 - 控制反转和依赖注入

反模式

其实我们已经提前预设我们想要传入logger了,已经在往依赖注入的这个方向在走了,如果不用依赖注入我们会怎么做呢?按照惯例,就是把logger直接写在renderer里,然后在renderer里根据当前环境判断执行不同的log。这样做的坏处是什么呢,首先肯定是过渡耦合,因为之前说了我们需要用到debugger,监控平台的API来log,那么写在renderer里无疑是把这两个依赖也加到了renderer里,那就是说我们限制了renderer的使用场景,并且以后如果这些依赖就什么改动我们需要修改renderer的源码。

优化

优化第一步首先是去耦,也就是考虑将logger抽离出来,但是不是说外面任意传一个logger进来就能用,传入的logger需要实现我们的接口,比如

interface Logger {
 log() {}
 warn() {}
 error() {}
}

这样做的好处在于renderer不再直接依赖于debugger和监控平台API,并且我们在编写logger的时候也不用在renderer里编码,它完全可以是一个独立的库,我们可以单独测试开源它,或者是拿开源的logger来用。其次logger由于是外部传入的,它除了接口的限制外,他可以做任何事情,不再受限于具体的框架,API。这就是解耦的好处,可以降低框架的复杂度和提高它的扩展性。

前面说到了我们其实有3个logger,本地的时候用的是浏览器的console,值需要格式化一下log的内容然后打印。在preview的时候用到的是APP debugger的logger,这个logger会将log的内容展示到用户界面上,用户能清楚的看到关键的APP运行的信息。最后就是生产环境的logger,这个封装了监控平台API的log,一般我们只上报error log,让我们能实时的监控APP运行状态。

结语

有时候你可能会想,这么做有必要吗,但当你之后发现解耦带给你的方便的时候,你会庆幸还好当时没有写死。当然完事都是有度的,有时候写死会比复杂的设计来得更快更实在,让你的代码帮助你更快的抢占先机。但是,想要一个可维护的代码库,我们考虑得还需要更多。

历史精选

  1. 如何在10分钟之内完成一个业务页面 – Vue的封装艺术
  2. 新手也能看懂的虚拟滚动实现方法
  3. Vue3有哪些不向下兼容的改变
  4. 精细控制transition细节 – 实现一个活泼的弹框
  5. 原文-我的小破站

原文链接:https://juejin.cn/post/7231932399101165624 作者:格砸

(0)
上一篇 2023年5月12日 上午10:35
下一篇 2023年5月12日 上午10:46

相关推荐

发表回复

登录后才能评论