背景
第一次聽說 SPI 是閱讀《軟件框架設計的藝術》,以后陸續在 Log4Net 和 Quartz.Net中發現了以這種形式組織代碼的方式,本位給出為什么要區分 SPI 和 API 的一個思考過程。
從面向接口編程說起
我們在“調用方”和“實現方”之間引入了“接口”,上圖沒有給出“接口”應該位於哪個“包”中,從純粹的可能性上考慮,我們有三種選擇:
- “接口”位於“調用方”所在的“包”中。
- “接口”位於“實現方”所在的“包”中。
- “接口”位於獨立的“包”中。
下面讓我們依次分析這三種可能性,如果現實中確實有這種可能性,不如我們就為其起個名字以方便交流。
“接口”位於“調用方”所在的“包”中
我們先想象一個場景,以倉儲的接口為例:
我們將“倉儲接口”放置於“領域層”這個“包”中,實現放在一個獨立的“包”中,我們看DDD大師的實現都是這樣子,現在來思考一下為什么這么做。
“領域層”的“領域服務”會依賴“倉儲接口”,“倉儲接口”也會依賴“聚合根”,這兩者都是除了“實現依賴”之外的依賴關系,如果將“接口”放到“倉儲實現”中就喪失了面向接口編程的意義(編譯也不會通過),如果放到“獨立層”中呢?會編譯不通過,出現雙向依賴了。
對於類似這種情況下接口,我們將其稱為“SPI”,全程為:service provider interface,“SPI”的規則如下:
- 概念上更依賴調用方。
- 組織上位於調用方所在的包中。
- 實現位於獨立的包中。
- 常見的例子是:插件模式的插件。
“接口”位於“實現方”所在的“包”中
我們先想象一個場景,以Unity提供的IUnityContainer接口為例,除了維護這個框架的團隊之外,我們沒有發現誰實現了這個接口,雖然理論上是可以實現這個接口的(如果能實現的話,我們何不自己弄額Ioc容器呢?)。
對於類似這種情況下的接口,我們將其稱作為“API”,“API”的規則如下:
- 概念上更接近實現方。
- 組織上位於實現方所在的包中。
- 實現和接口在一個包中。
“接口”位於獨立的“包”中
這里就不說場景了,如果一個“接口”在一個上下文是“API”,在另一個上下文是“SPI”,那么你就可以這么組織。
需要注意的事項
不管是SPI或API,接口都是可以組織到獨立的“包”中,這么做是否有意義,自己來做出決定了。
SPI和API也不一定是接口,我這里都是指狹義的具體的接口。
另外一張圖
備注
每一次思考都伴隨着收獲,也離不開和朋友們的交流,天更藍了。