在上一篇文章当中我们谈到开放封闭原则,这里我们要来谈谈依赖反转原则 Dependency inversion principle。先不谈定义,先来看范例。
延续上一篇文章的例子,我们有一间专门负责计算面积的公司,方法 calculateArea
定义如下:
const calculateArea = (object) => {
return object.getArea()
}
由於我们的生意做得太好了,所以有另外一家大公司主动来找我们合作,希望我们加入他们
这家大公司拥有 Tool
类别如下
class Tool {
calculate: Function
constructor(calculateFunction: Function) {
this.calculate = calculateFunction
}
}
只要在建立实例的时候把 calculateArea
方法给传入,这个 newTool 就成为一个新的、同样能计算面积的工具了!
const a = new Rectangle(13, 17)
const b = new Triangle(11, 19)
const newTool = new Tool(calculateArea)
newTool.calculate(a) // 221
newTool.calculate(b) // 104.5
於是乎,Tool
公司开始「依赖」着 calculateArea
部门来服务他的客户。
不过 calculateArea
这个部门也很有自己的想法。有一天突然想到,除了只回传面积结果之外,如果能够回传多一点的细节,譬如计算面积的总成本,也许会更好。所以 calculateArea
就把回传内容改成下面这样
const calculateArea = (object) => {
return {
cost: object.getArea() * 0.01,
result: object.getArea()
}
}
结果过没多久,公司就收到一堆人的抱怨,因为原本大家期待呼叫 Tool
的 calculate 方法会得到面积的数值,然而现在却得到了一个物件!
newTool.calculate(a) // { cost: 2.21, result: 221 }
newTool.calculate(b) // { cost: 1.045, result: 104.5 }
为了挽救商誉,公司紧急将 Tool
修改成下面这样
class Tool {
calculateFunction: Function
constructor(calculateFunction) {
this.calculateFunction = calculateFunction
}
calculate(object) {
return this.calculateFunction(object).result
}
}
最後好让使用者得到同样的结果
newTool.calculate(a) // 221
newTool.calculate(b) // 104.5
感谢工程师们的努力,公司再次平安度过了一天。然而这家公司还有其他许多的部门,每当这些部门更新或调整各自的方法的时候,Tool
也就会跟着忙着修改,工程师们也就有看起来作不完的事情可以做了。
等等,这好像违反了我们前面提到的「单一功能原则」和「开放封闭原则」,为什麽专门负责计算面积的方法更新,Tool
类别也要跟着修改呢?不能只修改一个地方就好了吗?
问题发生的原因是,Tool
的实作依赖着 calculateArea
方法,所以在 calculateArea
方法有修改的情况下,如果 Tool
想要维持同样的产出结果,那麽就必定需要跟着修改。
有没有什麽方法,可以让 Tool
不依赖 calculateArea
方法呢?也就是当 calculateArea
方法变动的时候,Tool
类别自己可以完全不用担心呢?
Tool
类别最终还是得靠 calculateArea
方法来计算出面积,所以不可能抛弃他,不过这次公司学乖了,主动跟个别部门并好规则:
「今天不管你各位怎麽计算面积、系统如何更新,我就是要看到数字,其他的我都不想看到」
讲完的同时,公司就提出了一个 AreaCalculator
型别,他定义了方法的输入型别和输出型别,分别是 Shape
和 number
type AreaCalculator = (a: Shape) => number;
接着,他继续规定,要传入 Tool
的方法,需要遵守AreaCalculator
型别的规定
class Tool {
calculate: AreaCalculator
constructor(calculateFunction: AreaCalculator) {
this.calculate = calculateFunction
}
}
这时候 calculateArea
只好摸摸鼻子,遵守了AreaCalculator
型别的规定,规定输入的型别是 Shape
而输出只能是 number
const calculateArea: AreaCalculator = (object: Shape): number => {
return object.getArea()
}
所以未来不管 calculateArea
如何变动,只要遵守着和 Tool
之间的约定 (AreaCalculator
型别),那麽 Tool
就不需要有任何变动。工程师们突然就失业了!
突然之间情势逆转,Tool
类别不再依赖着 calculateArea
方法,这就是「依赖反转」的现象。
所以,究竟什麽是依赖反转原则呢?
In object-oriented design, the dependency inversion principle is a specific form of loosely coupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states:
- High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
在物件导向程序设计当中,依赖反转原则是一种解耦的形式,根据这个原则执行的时候,高层次的模组 (module) 独立於低层次模组的执行细节。这个原则指出:
以刚刚的例子为例的话,就是
Tool
类别calculateArea
方法AreaCalculator
介面所以 Tool
和 calculateArea
两者都依赖 AreaCalculator
介面。从高层次模组的角度来看,他只知道要使用长得像是 AreaCalculator
的东西,但是不需要知道实际上会是什麽东西。从低层次模组的角度来看,必须执行 AreaCalculator
介面,也就是说,当中的执行细节,需要满足这个介面的要求。
「开放封闭原则」让我们能够在不修改(或降低修改)的情况下,持续因应变化扩充功能,而根据「依赖反转原则」,则可以让程序本身不会因为低层次的模组的改变,而需要修正。
回头看刚刚的例子,就是 Tool
能够处理的需求,可以根据传入的 function 进行功能上的扩充,同时透过 AreaCalculator
的设立,让Tool
避免受到低层次模组的影响。
>>: 用React刻自己的投资Dashboard Day13 - 制作分页(Pagination)功能
问题回答 Composition API 是以逻辑功能来分割程序码,像是写原生 JavaScript...
因为上一个自我挑战,我耍白痴,打完忘记按发表,所以只能再重新ㄌ呜呜 希望我这次不会再耍白痴了? 嘎油...
选择HTML标签元素 取得元素 getElementBy document.getElementBy...
Vue生命周期(Life Cycle) 每个实例从被初始化,挂载到DOM、更新,到最後被销毁的历程。...
PDO PDO 全称 PHP Data Object extension 是 PHP 5.1 开始提...