面試官問我如何理解 IOC 和 DI

來源 | https://github.com/Wscats/dependency-injection/blob/master/README.CN.md
IOC/DI
IOC,全稱 Inversion Of Control,控制反轉(zhuǎn)是面向?qū)ο缶幊痰囊环N設(shè)計(jì)思想,主要用來降低代碼之間的耦合度。 DI,全稱 Dependency Injection,依賴注入是 IOC 的具體實(shí)現(xiàn)。是指對象通過外部的注入,避免對象內(nèi)部自身實(shí)現(xiàn)外部依賴的實(shí)例化過程。
Implementation
@參數(shù)裝飾器使用方法:接收三個參數(shù) target: 對于靜態(tài)成員來說是類的構(gòu)造器,對于實(shí)例成員來說是類的原型鏈 key: 方法的名稱,注意是方法的名稱,而不是參數(shù)的名稱 index: 參數(shù)在方法中所處的位置的下標(biāo) @返回:返回的值將會被忽略
class C {constructor(@serviceA private a: A, @serviceB private b: B) {}}
所有參數(shù)裝飾器均由 createDecorator 方法創(chuàng)建,'A' 和 'B',均是該裝飾器的唯一標(biāo)識。
const serviceA = createDecorator("A");const serviceB = createDecorator("B");
裝飾器首先判斷是否被緩存,如果有被緩存則取出已經(jīng)緩存好的參數(shù)裝飾器,如果沒被緩存,則創(chuàng)建一個 serviceIdentifier 的參數(shù)裝飾器。
function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {if (_util.serviceIds.has(serviceId)) {return _util.serviceIds.get(serviceId) as ServiceIdentifier<T>;}}
serviceIdentifier 參數(shù)裝飾器只做了一件事就是觸發(fā) storeServiceDependency 把所有依賴項(xiàng)給存起來,存裝飾器本身 id,參數(shù)的下標(biāo) index 以及是否可選 optional。
const id = function serviceIdentifier(target: Ctor<T>, key: string, index: number): void {storeServiceDependency(id, target, index, false);};id.toString = () => serviceId;_util.serviceIds.set(serviceId, id);
storeServiceDependency 本質(zhì)是往 target 即 class C 上設(shè)置兩個靜態(tài)屬性 $di$target 和 $di$dependencies 上面分別存 target,自身還要再存一次自身 target 是為了判斷是否已經(jīng)存過依賴。
C.$di$target; // class CC.$di$dependencies[0].id.toString(); // A 或者 BC.$di$dependencies; // [{id: serviceIdentifier, index: 1, optional: false}, {id: serviceIdentifier, index: 0, optional: false}]
除了存在類上,還存在了 _util.serviceIds 上。
當(dāng)類聲明的時候,裝飾器就會被應(yīng)用,所以在有類被實(shí)例化之前依賴關(guān)系就已經(jīng)確定好了。
把 ts 編譯就可以證明這點(diǎn),可以看到 __decorate 在類聲明的時候,裝飾器就會被執(zhí)行了。
var C = /** @class */ (function() {function C(a, b) {this.a = a;this.b = b;}C = __decorate([__param(0, serviceA), __param(1, serviceB)], C);return C;})();
緊接著就到了 ServiceCollection,這里會將裝飾器作為 key 唯一標(biāo)識,實(shí)例化的類作為 value,全部存到 svrsCollection 中,svrsCollection 的實(shí)現(xiàn)也很簡單,直接用 Map 方法存起來。
const aInstance = new A();const bInstance = new B();const svrsCollection = new ServiceCollection();svrsCollection.set(serviceA, aInstance);svrsCollection.set(serviceB, bInstance);
后續(xù)只需要使用 get 方法并傳入對應(yīng)的參數(shù)裝飾器就可以獲取對應(yīng)的實(shí)例化好的類了。
svrsCollection.get(serviceA); // new A()svrsCollection.get(serviceB); // new B()
InstantiationService 是實(shí)現(xiàn)依賴注入的核心,它是以參數(shù)裝飾器,例如 serviceA 和 serviceB 等 ServiceIdentifier 為 key 在私有變量 services 中保存所有依賴注入的被實(shí)例化好的類。services 保存的是 svrsCollection。
const instantiationService = new InstantiationService(svrsCollection);
它暴露了三個公開方法:
createInstance 該方法接受一個類以及構(gòu)造該類的非依賴注入?yún)?shù),然后創(chuàng)建該類的實(shí)例。
invokeFunction 該方法接受一個回調(diào)函數(shù),該回調(diào)函數(shù)通過 acessor 參數(shù)可以訪問該 InstantiationService 中的所有依賴注入項(xiàng)。
createChild 該方法接受一個依賴項(xiàng)集合,并創(chuàng)造一個新的 InstantiationService 說明 vscode 的依賴注入機(jī)制也是有層次的。
createInstance 方法是實(shí)例化的核心方法:
const cInstance = instantiationService.createInstance(C, "L", "R") as C;
首先是獲取 ctorOrDescriptor 也就是類 class C 和需要傳入非依賴注入的參數(shù) rest。
const result = this.createCtorInstance(ctorOrDescriptor, rest);
然后使用 getServiceDependencies 把掛載 class C 靜態(tài)屬性的 $di$dependencies 給獲取出來并排序,因?yàn)榇娴臅r候順序是倒序的。
const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
取出來的依賴項(xiàng) serviceDependencies 主要是為了遍歷并獲取里面的參數(shù)裝飾器 serviceA 和 serviceB。
const serviceArgs: any[] = [];for (const dependency of serviceDependencies) {const serviceInstance = this.getOrCreateServiceInstance(dependency.id);serviceArgs.push(serviceInstance);}
getOrCreateServiceInstance 本質(zhì)就是從 services 即 svrsCollection 中獲取實(shí)例化好的類。
const instanceOrDesc = this.services.get(id);// 相當(dāng)于 id 即參數(shù)裝飾器// svrsCollection.get(id);
當(dāng)把所有的這些實(shí)例化好的類取出來放到 serviceArgs 中后,由于參數(shù)裝飾器是類實(shí)例化的時候就執(zhí)行完并收集好依賴項(xiàng)。
所以 serviceArgs 就是對應(yīng) ctor 即 class C 需要注入的依賴參數(shù),合并非依賴參數(shù)就能幫助我們成功實(shí)例化好我們的 ctor 類。
new ctor(...[...serviceArgs, ...args]);
感謝你的閱讀,祝編程愉快!
學(xué)習(xí)更多技能
請點(diǎn)擊下方公眾號
![]()

