VSCode中的依賴注入怎么實(shí)現(xiàn)

這篇文章主要講解了“VSCode中的依賴注入怎么實(shí)現(xiàn)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“VSCode中的依賴注入怎么實(shí)現(xiàn)”吧!

創(chuàng)新互聯(lián)"三網(wǎng)合一"的企業(yè)建站思路。企業(yè)可建設(shè)擁有電腦版、微信版、手機(jī)版的企業(yè)網(wǎng)站。實(shí)現(xiàn)跨屏營(yíng)銷,產(chǎn)品發(fā)布一步更新,電腦網(wǎng)絡(luò)+移動(dòng)網(wǎng)絡(luò)一網(wǎng)打盡,滿足企業(yè)的營(yíng)銷需求!創(chuàng)新互聯(lián)具備承接各種類型的網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站項(xiàng)目的能力。經(jīng)過(guò)十多年的努力的開拓,為不同行業(yè)的企事業(yè)單位提供了優(yōu)質(zhì)的服務(wù),并獲得了客戶的一致好評(píng)。

依賴注入介紹

如果有這樣一個(gè)模塊 A,它的實(shí)現(xiàn)依賴另一個(gè)模塊 B 的能力,那么應(yīng)該如何設(shè)計(jì)呢?很簡(jiǎn)單,我們可以在 A 模塊的構(gòu)造函數(shù)中實(shí)例化模塊 B,這樣就可以在模塊 A 內(nèi)部使用模塊 B 的能力了。

class A {
  constructor() {
    this.b = new B();
  }
}

class B {}

const a = new A();

但是這樣做有兩個(gè)問(wèn)題,一是模塊 A 的實(shí)例化過(guò)程中,需要手動(dòng)實(shí)例化模塊 B,而且如果模塊 B 的依賴關(guān)系發(fā)生變化,那么也需要修改模塊 A 的構(gòu)造函數(shù),導(dǎo)致代碼耦合。

二是在復(fù)雜項(xiàng)目中,我們?cè)趯?shí)例化模塊 A 時(shí),難以判斷模塊 B 是否被其他模塊依賴而已經(jīng)實(shí)例化過(guò)了,從而可能將模塊 B 多次實(shí)例化。若模塊 B 較重或者需要為單例設(shè)計(jì),這將帶來(lái)性能問(wèn)題。

因此,更好的方式是,將所有模塊的實(shí)例化交給外層框架,由框架統(tǒng)一管理模塊的實(shí)例化過(guò)程,這樣就可以解決上述兩個(gè)問(wèn)題。

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);

這種將依賴對(duì)象通過(guò)外部注入,避免在模塊內(nèi)部實(shí)例化依賴的方式,稱為依賴注入 (Dependencies Inject, 簡(jiǎn)稱 DI)。這在軟件工程中是一種常見(jiàn)的設(shè)計(jì)模式,我們?cè)?Java 的 Spring,JS 的 Angular,Node 的 NestJS 等框架中都可以看到這種設(shè)計(jì)模式的應(yīng)用。

當(dāng)然,在實(shí)際應(yīng)用中,由于模塊眾多,依賴復(fù)雜,我們很難像上面的例子一樣,規(guī)劃出來(lái)每個(gè)模塊的實(shí)例化時(shí)機(jī),從而編寫模塊實(shí)例化順序。并且,許多模塊可能并不需要第一時(shí)間被創(chuàng)建,需要按需實(shí)例化,因此,粗暴的統(tǒng)一實(shí)例化是不可取的。

因此我們需要一個(gè)統(tǒng)一的框架來(lái)分析并管理所有模塊的實(shí)例化過(guò)程,這就是依賴注入框架的作用。

借助于 TypeScript 的裝飾器能力,VSCode 實(shí)現(xiàn)了一個(gè)極為輕量化的依賴注入框架。我們可以先來(lái)簡(jiǎn)單實(shí)現(xiàn)一下,解開這個(gè)巧妙設(shè)計(jì)的神秘面紗。

最簡(jiǎn)依賴注入框架設(shè)計(jì)

實(shí)現(xiàn)一個(gè)依賴注入框架只需要兩步,一個(gè)是將模塊聲明并注冊(cè)到框架中進(jìn)行管理,另一個(gè)是在模塊構(gòu)造函數(shù)中,聲明所需要依賴的模塊有哪些。

我們先來(lái)看模塊的注冊(cè)過(guò)程,這需要 TypeScript 的類裝飾器能力。我們?cè)谧⑷霑r(shí),只需要判斷模塊是否已經(jīng)注冊(cè),如果沒(méi)有注冊(cè),將模塊的 id(這里簡(jiǎn)化為模塊 Class 名稱)與類型傳入即可完成單個(gè)模塊的注冊(cè)。

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

之后我們?cè)賮?lái)看看模塊是如何聲明依賴的,這需要 TypeScript 的屬性裝飾器能力。我們?cè)谧⑷霑r(shí),先判斷依賴的模塊是否已經(jīng)被實(shí)例化,如果沒(méi)有,則將依賴模塊進(jìn)行實(shí)例化,并存入框架中管理。最終返回已經(jīng)被實(shí)例化完成的模塊實(shí)例。

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);
  };
}

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

export class ServiceCollection {
  readonly providers = new Map<string, any>();
  readonly dependencies = new Map<string, any>();
}

const collection = new ServiceCollection();
export default collection;

這樣,一個(gè)最簡(jiǎn)化的依賴注入框架就完成了。由于保存了模塊的類型與實(shí)例,它實(shí)現(xiàn)了模塊的按需實(shí)例化,無(wú)需在項(xiàng)目啟動(dòng)時(shí)就初始化所有模塊。

我們可以嘗試調(diào)用它,以上面舉出的例子為例:

@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();

無(wú)需知曉模塊 A,B 的實(shí)例化時(shí)機(jī),直接初始化任何一個(gè)模塊,框架會(huì)自動(dòng)幫你找到并實(shí)例化好所有依賴的模塊。

VSCode 的依賴收集實(shí)現(xiàn)

上面介紹了一個(gè)依賴注入框架的最簡(jiǎn)實(shí)現(xiàn)。但當(dāng)我們真正閱讀 VSCode 的源碼時(shí),我們發(fā)現(xiàn) VSCode 中的依賴注入框架貌似并不是這樣消費(fèi)的。

例如在下面這段鑒權(quán)服務(wù)中,我們發(fā)現(xiàn)該類并沒(méi)有@injectable()作為類的依賴收集,并且依賴服務(wù)也直接用其類名作為修飾器,而不是@inject()。

// src\vs\workbench\services\authentication\browser\authenticationService.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
  ) {}
}

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

通過(guò) ServiceIdentifier 作為 id,而不是簡(jiǎn)單粗暴地通過(guò)類名稱作為 id 注冊(cè) Service,有利于處理項(xiàng)目中一個(gè) interface 可能存在多態(tài)實(shí)現(xiàn),需要同時(shí)多個(gè)同名類實(shí)例的問(wèn)題。

此外,在構(gòu)造 ServiceIdentifier 時(shí),我們便可以將該類聲明注入框架,而無(wú)需@injectable()顯示調(diào)用了。

那么,這樣一個(gè) ServiceIdentifier 該如何構(gòu)造呢?

// src\vs\platform\instantiation\common\instantiation.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 裝飾的類在運(yùn)行時(shí),將收集該類的依賴,注入到框架中。
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;
  }
}

我們僅需通過(guò)createDecorator方法為類創(chuàng)建一個(gè)唯一的ServiceIdentifier,并將其作為修飾符即可。

以上面的 AuthenticationService 為例,若所依賴的 ActivityService 需要變更多態(tài)實(shí)現(xiàn),僅需修改 ServiceIdentifier 修飾符確定實(shí)現(xiàn)方式即可,無(wú)需更改業(yè)務(wù)的調(diào)用代碼。

export const IActivityServicePlanA = createDecorator<IActivityService>("IActivityServicePlanA");
export const IActivityServicePlanB = createDecorator<IActivityService>("IActivityServicePlanB");
export interface IActivityService {...}

export class AuthenticationService {
  constructor(
    @IActivityServicePlanA private readonly activityService: IActivityService,
  ) {}
}

循環(huán)依賴問(wèn)題

模塊之間的依賴關(guān)系是有可能存在循環(huán)依賴的,比如 A 依賴 B,B 依賴 A。這種情況下進(jìn)行兩個(gè)模塊的實(shí)例化會(huì)造成死循環(huán),因此我們需要在框架中加入循環(huán)依賴檢測(cè)機(jī)制來(lái)進(jìn)行規(guī)避。

本質(zhì)上,一個(gè)健康的模塊依賴關(guān)系就是一個(gè)有向無(wú)環(huán)圖(DAG),我們之前介紹過(guò)有向無(wú)環(huán)圖在 excel 表格函數(shù)中的應(yīng)用,放在依賴注入框架的設(shè)計(jì)中也同樣適用。

我們可以通過(guò)深度優(yōu)先搜索(DFS)來(lái)檢測(cè)模塊之間的依賴關(guān)系,如果發(fā)現(xiàn)存在循環(huán)依賴,則拋出異常。

// src/vs/platform/instantiation/common/instantiationService.ts
while (true) {
  let roots = graph.roots();

  // if there is no more roots but still
  // nodes in the graph we have a cycle
  if (roots.length === 0) {
    if (graph.length !== 0) {
      throwCycleError();
    }
    break;
  }

  for (let root of roots) {
    // create instance and overwrite the service collections
    const instance = this._createInstance(root.data.desc, []);
    this._services.set(root.data.id, instance);
    graph.removeNode(root.data);
  }
}

該方法通過(guò)獲取圖節(jié)點(diǎn)的出度,將該類的全部依賴提取出來(lái)作為roots,然后逐個(gè)實(shí)例化,并從途中剝離該依賴節(jié)點(diǎn)。由于依賴樹的構(gòu)建是逐層依賴的,因此按順序?qū)嵗纯?。?dāng)發(fā)現(xiàn)該類的所有依賴都被實(shí)例化后,圖中仍存在節(jié)點(diǎn),則認(rèn)為存在循環(huán)依賴,拋出異常。

感謝各位的閱讀,以上就是“VSCode中的依賴注入怎么實(shí)現(xiàn)”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)VSCode中的依賴注入怎么實(shí)現(xiàn)這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

當(dāng)前題目:VSCode中的依賴注入怎么實(shí)現(xiàn)
標(biāo)題URL:http://muchs.cn/article22/ihijcc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站服務(wù)器托管、響應(yīng)式網(wǎng)站企業(yè)網(wǎng)站制作、網(wǎng)站改版網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

小程序開發(fā)