从 Javascript 看依赖倒置

876 阅读4分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

前言

上一篇讲到了 JavaScript 怎么认识面向对象以及单一职责原则的应用,这一篇来讲一下依赖倒置原则。

什么是依赖倒置原则呢,网上一查,最常见的一句话就是,两个类(或者接口)不相互依赖而是依赖一个抽象类(或者接口)。多看几遍都快要背下来了,可是这么拗口的话怎么去理解。举一个例子就是 A 和 B 说话,但是他们不直接说话,而是通过 C 进行说话(这是什么剧情的既视感)。

这个 C 就是中间的工具人,接下来进入一个小剧场。

小剧场

某一天,A 对 C 说,你碰到 B 帮我告诉 B “******”。C 说好的,我记下了,然后等 B 来找 C 的时候,C说,这是 A 给你留下的 “*******”,你拿走吧。

一个很简单的小剧场,其实这就是依赖倒置原则的实现过程,就是 A 不直接和 B 碰面,而且通过留纸条的方式,C 就是管理纸条的人。

如果不这么抽象,稍微拉回现实一点,就是 A 不直接和 B 进行数据交流,而是将数据存在 C 那里,然后 B 是通过向 C 拿数据的过程。

用代码展示

用代码来写怎么实现呢?

    const C = {
        cache: {}
        setData(data) {
            this.cache.temp1 = data
        },
        getData(key) {
            return this.cache[key]
        }
    }

    async function A() {
        const { data } = await fetchApi()
        C.setData(data)
    }

    function B() {
        cosnt data = C.getData('temp1')
        doSomeThing(data)
    }

首先我们有一个 C 来存放数据,A 是获取了数据,然后存起来,B 要用这个数据是通过 C 来拿,而不是调用 A 或者通过 A 传参。这样哪怕接下来是换成 D 要使用这个数据也不用修改 A,来让 A 和 D 讲话。

只需要增加一个 D 方法

    function D() {
        const data = C.getData('temp1')
        doSomeThingElse(data)
    }

这种思想在很多地方都很常见,比如在 vue 项目中,我们通过接口请求数据,请求到了之后,存到 this.xxxx,然后渲染视图或者在下一个函数里面读取,然后做其他操作。

vuex 也是基于这种原则,两个组件不直接通信,而是可以通过存到 vuex 里面。这样无论组件怎么变化,如果想要这个数据就从 vuex 里面拿,如果要提供这个数据,就往 vuex 里面存。不需要关注和谁通信。

这只是依赖倒置这种思想的使用。依赖倒置的解释是依赖于抽象而不依赖于具体实现。

这样做的好处是什么呢?

依赖倒置原则的好处

依赖倒置原则可以让我们的代码更解耦。

假设有以下需求,第一获取到文章数据,第二把文章的某些字进行替换,第三将文章渲染到视图上。

    async function getArticle() {
        let article = await fetchArticle()
        article = C.replaceStr(article)
        B.render(article)
    }

看起来还很容易,那现在更改需求,我们不要替换某些字了,而是引入一个翻译器,将文章翻译成其他语言,然后再渲染到视图上。

这时我们就会发现我们需要更改 getArticle,而且这个方法以及不满足单一职责原则,如果我们要引入其他操作文章的方法,就会让这个函数越来越大。如果我们最后不是渲染视图,我们可能是调接口上传处理后的数据,我们都需要改这个方法,如果加上不同类型用不同方法,我们就加上更多的条件判断,而获取文章,修改文章,处理修改后的文章应该是三个独立的棵替换的模块。

    const store = {}
    
    // 获取文章,不会改变
    async function getArticle() {
        const article = await fetchArticle()
        handleArticle(article)
        render()
    }
    // 依赖一个外部存储对象,也不会改变
    function render() {
        const article = store.article
        B.render()
    }
    // 可选操作方法
    function replaceStr(article) {
        // do something
        
        return article
    }
    // 可选操作方法
    function translate(article) {
        // do something
        
        return article
    }
    // 操作
    handleArticle(article) {
        article = translate(article)
        
        store.article = article
    }
    
    // or
    
    handleArticle(article) {
        article = replaceStr(article)
        store.article = article
    }
    
    // or
    
    handleArticle(article) {
        article = replaceStr(article)
        article = translate(article)
        store.article = article
    }

上面可以根据情况来更换 handleArticle,而获取数据,渲染视图的方法不需要再修改,让代码更容易维护。

我们可以随时更改我们的需求,如果我们不要渲染视图的话,我们只需要将 render 换掉,假如我们要将文章上传,我们只需要写一个方法,从 store.article 里面获取文章即可。

这样一来功能与功能之间就不再那么耦合了。

从某个角度来说它们都依赖于 "文章数据" 这个实体。这算不算也是一种依赖倒置呢。))))

以上就是从 JavaScript 的角度来看依赖倒置原则。设计是一门美学,好的代码不仅用起来优雅,项目复杂的时候也不会加班到头疼。