站長資訊網
        最全最豐富的資訊網站

        詳細分析一下VSCode中的依賴注入

        詳細分析一下VSCode中的依賴注入

        php入門到就業線上直播課:進入學習
        Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調試工具:點擊使用

        在閱讀 VSCode 代碼的過程中,我們會發現每一個模塊中都有大量裝飾器的使用,用來裝飾模塊以及其中依賴的模塊變量。這樣做的目的是什么呢?在這一篇中我們來詳細分析一下。【推薦學習:vscode教程、編程視頻】

        依賴注入介紹

        如果有這樣一個模塊 A,它的實現依賴另一個模塊 B 的能力,那么應該如何設計呢?很簡單,我們可以在 A 模塊的構造函數中實例化模塊 B,這樣就可以在模塊 A 內部使用模塊 B 的能力了。

        class A {   constructor() {     this.b = new B();   } }  class B {}  const a = new A();
        登錄后復制

        但是這樣做有兩個問題,一是模塊 A 的實例化過程中,需要手動實例化模塊 B,而且如果模塊 B 的依賴關系發生變化,那么也需要修改模塊 A 的構造函數,導致代碼耦合。

        二是在復雜項目中,我們在實例化模塊 A 時,難以判斷模塊 B 是否被其他模塊依賴而已經實例化過了,從而可能將模塊 B 多次實例化。若模塊 B 較重或者需要為單例設計,這將帶來性能問題。

        因此,更好的方式是,將所有模塊的實例化交給外層框架,由框架統一管理模塊的實例化過程,這樣就可以解決上述兩個問題。

        class A {   constructor(private b: B) {     this.b = b;   } }  class B {}  class C {   constructor(private a: A, private b: B) {     this.b = b;   } }  const b = new B(); const a = new A(b); const c = new C(a, b);
        登錄后復制

        這種將依賴對象通過外部注入,避免在模塊內部實例化依賴的方式,稱為依賴注入 (Dependencies Inject, 簡稱 DI)。這在軟件工程中是一種常見的設計模式,我們在 Java 的 Spring,JS 的 Angular,Node 的 NestJS 等框架中都可以看到這種設計模式的應用。

        當然,在實際應用中,由于模塊眾多,依賴復雜,我們很難像上面的例子一樣,規劃出來每個模塊的實例化時機,從而編寫模塊實例化順序。并且,許多模塊可能并不需要第一時間被創建,需要按需實例化,因此,粗暴的統一實例化是不可取的。

        因此我們需要一個統一的框架來分析并管理所有模塊的實例化過程,這就是依賴注入框架的作用。

        借助于 TypeScript 的裝飾器能力,VSCode 實現了一個極為輕量化的依賴注入框架。我們可以先來簡單實現一下,解開這個巧妙設計的神秘面紗。

        最簡依賴注入框架設計

        實現一個依賴注入框架只需要兩步,一個是將模塊聲明并注冊到框架中進行管理,另一個是在模塊構造函數中,聲明所需要依賴的模塊有哪些。

        我們先來看模塊的注冊過程,這需要 TypeScript 的類裝飾器能力。我們在注入時,只需要判斷模塊是否已經注冊,如果沒有注冊,將模塊的 id(這里簡化為模塊 Class 名稱)與類型傳入即可完成單個模塊的注冊。

        export function Injectable(): ClassDecorator {   return (Target: Class): any => {     if (!collection.providers.has(Target.name)) {       collection.providers.set(Target.name, target);     }     return target;   }; }
        登錄后復制

        之后我們再來看看模塊是如何聲明依賴的,這需要 TypeScript 的屬性裝飾器能力。我們在注入時,先判斷依賴的模塊是否已經被實例化,如果沒有,則將依賴模塊進行實例化,并存入框架中管理。最終返回已經被實例化完成的模塊實例。

        export function Inject(): PropertyDecorator {   return (target: Property, propertyKey: string) => {      const instance = collection.dependencies.get(propertyKey);     if (!instance) {       const DependencyProvider: Class = collection.providers.get(propertyKey);       collection.dependencies.set(propertyKey, new DependencyProvider());     }      target[propertyKey] = collection.dependencies.get(propertyKey);   }; }
        登錄后復制

        最后只需要保證框架本身在項目運行前完成實例化即可。(在例子中表示為 injector)

        export class ServiceCollection {   readonly providers = new Map<string, any>();   readonly dependencies = new Map<string, any>(); }  const collection = new ServiceCollection(); export default collection;
        登錄后復制

        這樣,一個最簡化的依賴注入框架就完成了。由于保存了模塊的類型與實例,它實現了模塊的按需實例化,無需在項目啟動時就初始化所有模塊。

        我們可以嘗試調用它,以上面舉出的例子為例:

        @injectable() class A {   constructor(@inject() private b: B) {     this.b = b;   } }  @injectable() class B {}  class C {   constructor(@inject() private a: A, @inject() private b: B) {     this.b = b;   } }  const c = new C();
        登錄后復制

        無需知曉模塊 A,B 的實例化時機,直接初始化任何一個模塊,框架會自動幫你找到并實例化好所有依賴的模塊。

        VSCode 的依賴收集實現

        上面介紹了一個依賴注入框架的最簡實現。但當我們真正閱讀 VSCode 的源碼時,我們發現 VSCode 中的依賴注入框架貌似并不是這樣消費的。

        例如在下面這段鑒權服務中,我們發現該類并沒有@injectable()作為類的依賴收集,并且依賴服務也直接用其類名作為修飾器,而不是@inject()

        // srcvsworkbenchservicesauthenticationbrowserauthenticationService.ts export class AuthenticationService extends Disposable implements IAuthenticationService {   constructor(     @IActivityService private readonly activityService: IActivityService,     @IExtensionService private readonly extensionService: IExtensionService,     @IStorageService private readonly storageService: IStorageService,     @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,     @IDialogService private readonly dialogService: IDialogService,     @IQuickInputService private readonly quickInputService: IQuickInputService   ) {} }
        登錄后復制

        其實這里的修飾符并不是真正指向類名,而是一個同名的資源描述符 id(VSCode 中稱之為 ServiceIdentifier),通常使用字符串或 Symbol 標識。

        通過 ServiceIdentifier 作為 id,而不是簡單粗暴地通過類名稱作為 id 注冊 Service,有利于處理項目中一個 interface 可能存在多態實現,需要同時多個同名類實例的問題。

        此外,在構造 ServiceIdentifier 時,我們便可以將該類聲明注入框架,而無需@injectable()顯示調用了。

        那么,這樣一個 ServiceIdentifier 該如何構造呢?

        // srcvsplatforminstantiationcommoninstantiation.ts /**  * The *only* valid way to create a {{ServiceIdentifier}}.  */ export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {    if (_util.serviceIds.has(serviceId)) {     return _util.serviceIds.get(serviceId)!;   }    const id = <any>function (target: Function, key: string, index: number): any {     if (arguments.length !== 3) {       throw new Error('@IServiceName-decorator can only be used to decorate a parameter');     }     storeServiceDependency(id, target, index);   };    id.toString = () => serviceId;    _util.serviceIds.set(serviceId, id);   return id; }  // 被 ServiceIdentifier 裝飾的類在運行時,將收集該類的依賴,注入到框架中。 function storeServiceDependency(id: Function, target: Function, index: number): void {   if ((target as any)[_util.DI_TARGET] === target) {     (target as any)[_util.DI_DEPENDENCIES].push({ id, index });   } else {     (target as any)[_util.DI_DEPENDENCIES] = [{ id, index }];     (target as any)[_util.DI_TARGET] = target;   } }
        登錄后復制

        我們僅需通過createDecorator方法為類創建一個唯一的ServiceIdentifier,并將其作為修飾符即可。

        以上面的 AuthenticationService 為例,若所依賴的 ActivityService 需要變

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 精品国产a∨无码一区二区三区| 精品国产第一国产综合精品| 午夜精品久久久久久毛片| 久久九九有精品国产23百花影院| 亚洲精品亚洲人成在线观看| 国产精品麻豆VA在线播放| 国产精品一区二区av| 日韩在线精品一二三区| 国产精品美女网站| 一级成人精品h| 国产成人精品精品欧美| 精品久久久久久久久午夜福利| 四虎精品免费永久免费视频| 国产色精品vr一区区三区| 久久久国产精品网站| 成人无码精品1区2区3区免费看| 日本aⅴ精品中文字幕| 亚洲福利精品电影在线观看| 九色精品视频在线观看| 国产精品嫩草影院久久| 午夜精品福利视频| 久久精品国内一区二区三区| 久久精品国产免费| 国内精品久久九九国产精品| 99免费精品视频| 国产精品久久午夜夜伦鲁鲁| 久久久精品人妻一区二区三区四 | 久久se精品一区二区影院 | 福利姬在线精品观看| 久久精品国产亚洲av麻豆色欲| 在线涩涩免费观看国产精品| 四虎国产精品成人| 欧美亚洲成人精品| 少妇亚洲免费精品| 野狼第一精品社区| 亚洲精品国产精品乱码不99| 亚洲国产另类久久久精品小说| 亚洲精品制服丝袜四区| 无码国内精品人妻少妇蜜桃视频| 蜜国产精品jk白丝AV网站| 久久精品www|