从 JavaScript 看迪米特原则

172 阅读3分钟

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

前言

之前说了单一职责原则,依赖倒置原则,接口隔离原则。这篇讲一下迪米特原则。

迪米特原则

迪米特原则是类只与他的直接朋友进行沟通。直接朋友例如传入的参数。但是在 JavaScript 里先不说直接朋友,类的概念都不一样。从函数的角度来看就是和依赖倒置差不多的想法。都是将两个模块解耦,而解耦的方法就是增加一个中间人。

JavaScript 实现迪米特原则可以使用一个中间人来传递信息。比如商品支付功能,首先我们会有一个管理商品的模块,然后还有一个支付的模块。如果是耦合的做法那就是在商品信息里面嵌套支付模块。比如以下代码

    function goodsModule() {
        this.goods = null
    }
    
    goodsModule.prototype = {
        getGoods() {},
        getGoodsCategory() {},
        addGoods() {},
        pay() {},
        computedDiscount() {},
        customPrice() {}
    }

稍微好一点的就是把支付单独提取出来。通过抽象一个统一的支付方法,然后根据传入的模块不同来改变支付的内部行为。

    function goodsModule(module) {
        this.payModule = module
    }
    
    goodsModule.prototype = {
        xxx() {
            this.payModule.pay() {}
        }
    }
    
    function payModule() {}

但这样商品和支付是耦合在一起的,如果我们要使用支付,我们就要通过商品。当然可以另外再写一个模块然后在里面实现支付,起码是复用了支付。那商品和支付再解耦是怎么样呢。这时候就要引入一个第三方的平台,我们可以叫他 controller。controller是一个统筹全部功能的平台,他可以实现其他不同的功能,引入不同的函数,并且提供切换他们的方法。做这么大一个容器肯定是不只是能做商品支付,但例子里只讲商品支付的使用。

值得注意的一点是当一个中间人非常厉害的时候,可能他本身也是有一定维护成本的。

    function controller() {
        this.module = {}
    }
    
    controller.prototype = {
        add(module) {
            this.module[module.name] = module
        },
        pay() {
            const goodsInfo = this.module.goodsModule.getGoods()
            this.module.payModule.pay(goodsInfo)
        }
    }

这个控制器怎么实现也好,怎么提供这个方法也好。现在这些模块的直接朋友不是其他模块,而是这个控制器,这样无论是什么商品,什么样的支付方式,还是说加入其他功能,都能够以插件的形式实现。

使用迪米特法则还要注意就是暴露只应该暴露的东西,设置什么读取什么都通过 getxxx,setxxx方法来实现。将模块的一些属性方法设置为私有的属性和方法。具体实现可以通过 iife + 闭包来模拟私有属性和方法。

迪米特法则可能会发生的问题就是中间人太多或者太强大,多和强大是两个极端。太多的使用中间人也是会提高系统的复杂性的,所以过度设计不可取。

结尾

代码的设计是抽象,以解耦为目标,不少设计模式都是基于面向对象的几大原则实现的。比如门面模式基于迪米特原则。那 JavaScript 如何写出解耦的代码呢?