Rumah >alat pembangunan >VSCode >Analisis terperinci suntikan pergantungan dalam VSCode

Analisis terperinci suntikan pergantungan dalam VSCode

青灯夜游
青灯夜游ke hadapan
2022-11-24 21:25:412429semak imbas

Analisis terperinci suntikan pergantungan dalam VSCode

Dalam proses membaca kod VSCode, kita akan mendapati terdapat sejumlah besar penghias yang digunakan dalam setiap modul untuk menghiasi modul dan modul-modulnya. bergantung kepada. Apakah tujuan melakukan ini? Dalam artikel ini kami akan menganalisisnya secara terperinci. [Pembelajaran yang disyorkan: Tutorial vscode, Video pengaturcaraan]

Pengenalan kepada Suntikan Ketergantungan

Jika terdapat modul A sedemikian, pelaksanaannya bergantung pada satu lagi Modul B mempunyai keupayaan, jadi bagaimana ia harus direka bentuk? Secara ringkasnya, kita boleh membuat instantiate modul B dalam pembina modul A, supaya kita boleh menggunakan keupayaan modul B di dalam modul A.

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

class B {}

const a = new A();

Tetapi terdapat dua masalah dengan ini Pertama, semasa proses instantiasi modul A, modul B perlu dibuat secara manual, dan jika kebergantungan modul B berubah, modul A juga perlu. diubah suai, yang membawa kepada gandingan kod.

Kedua, dalam projek yang kompleks, apabila kita membuat instantiat modul A, adalah sukar untuk menentukan sama ada modul B bergantung pada modul lain dan telah digunakan, jadi modul B boleh digunakan beberapa kali. Jika modul B lebih berat atau perlu direka bentuk sebagai singleton, ini akan menyebabkan masalah prestasi.

Oleh itu, cara yang lebih baik ialah menyerahkan instantiasi semua modul kepada rangka kerja luar, dan biarkan rangka kerja menguruskan proses instantiasi modul secara seragam, supaya kedua-dua masalah di atas dapat diselesaikan.

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

Kaedah menyuntik objek bergantung dari luar untuk mengelakkan kebergantungan instantiate di dalam modul dipanggil Dependencies Inject (DI). Ini ialah corak reka bentuk biasa dalam kejuruteraan perisian Kita boleh melihat aplikasi corak reka bentuk ini dalam rangka kerja seperti Java's Spring, JS's Angular, dan Node's NestJS.

Sudah tentu, dalam aplikasi sebenar, disebabkan bilangan modul yang banyak dan kebergantungan yang kompleks, sukar bagi kami untuk merancang pemasaan instantiasi setiap modul dan menulis jujukan instantiasi modul seperti contoh di atas. Selain itu, banyak modul mungkin tidak perlu dibuat pada kali pertama dan perlu dibuat instantiat atas permintaan Oleh itu, instantiasi bersatu kasar tidak digalakkan.

Jadi kami memerlukan rangka kerja bersatu untuk menganalisis dan mengurus proses instantiasi semua modul Ini adalah peranan rangka kerja suntikan pergantungan.

Dengan bantuan keupayaan penghias TypeScript, VSCode melaksanakan rangka kerja suntikan pergantungan yang sangat ringan. Mula-mula kita boleh melaksanakannya secara ringkas untuk merungkai misteri reka bentuk yang bijak ini.

Reka bentuk rangka kerja suntikan pergantungan yang paling mudah

Ia hanya mengambil dua langkah untuk melaksanakan rangka kerja suntikan pergantungan Satu ialah mengisytiharkan dan mendaftarkan modul ke dalam rangka kerja untuk pengurusan, dan satu lagi adalah dalam pembina modul Istiharkan modul yang anda perlukan untuk bergantung.

Mari kita lihat dahulu proses pendaftaran modul, yang memerlukan keupayaan penghias kelas TypeScript. Apabila menyuntik, kita hanya perlu menentukan sama ada modul telah didaftarkan. Jika tidak, memasukkan id modul (di sini dipermudahkan kepada nama Kelas modul) dan jenis boleh melengkapkan pendaftaran modul tunggal.

export function Injectable(): ClassDecorator {
  return (Target: Class): any => {
    if (!collection.providers.has(Target.name)) {
      collection.providers.set(Target.name, target);
    }
    return target;
  };
}
Seterusnya, mari kita lihat cara modul mengisytiharkan kebergantungan, yang memerlukan keupayaan penghias harta TypeScript. Apabila menyuntik, kami mula-mula menentukan sama ada modul bergantung telah dijadikan instantiated Jika tidak, modul bergantung akan instantiated dan disimpan dalam rangka kerja untuk pengurusan. Akhirnya mengembalikan contoh modul yang telah digunakan.

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);
  };
}
Akhir sekali, anda hanya perlu memastikan rangka kerja itu sendiri dibuat seketika sebelum projek dijalankan. (Diwakili sebagai penyuntik dalam contoh)

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

const collection = new ServiceCollection();
export default collection;
Dengan cara ini, rangka kerja suntikan kebergantungan yang paling mudah dilengkapkan. Memandangkan jenis modul dan kejadian disimpan, ia membolehkan instantiasi atas permintaan modul tanpa perlu memulakan semua modul apabila projek dimulakan.

Kita boleh cuba memanggilnya, mengambil contoh yang diberikan di atas:

@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();
Tidak perlu mengetahui pemasaan instantiasi modul A dan B, hanya mulakan mana-mana modul secara langsung, dan rangka kerja secara automatik akan Membantu anda mencari dan membuat instantiate semua modul bergantung.

Pelaksanaan pengumpulan kebergantungan VSCode

Di atas memperkenalkan pelaksanaan paling mudah bagi rangka kerja suntikan kebergantungan. Tetapi apabila kami benar-benar membaca kod sumber VSCode, kami mendapati bahawa rangka kerja suntikan pergantungan dalam VSCode nampaknya tidak digunakan dengan cara ini.

Sebagai contoh, dalam perkhidmatan pengesahan di bawah, kami mendapati bahawa kelas tidak mempunyai

sebagai koleksi kebergantungan kelas dan perkhidmatan bergantung juga secara langsung menggunakan nama kelasnya sebagai penghias dan bukannya @injectable(). @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
  ) {}
}
Malah, pengubah suai di sini sebenarnya tidak menunjuk kepada nama kelas, tetapi id deskriptor sumber dengan nama yang sama (dipanggil

dalam VSCode), yang biasanya dikenal pasti melalui rentetan atau Simbol. ServiceIdentifier

Gunakan

sebagai id dan bukannya hanya mendaftarkan Perkhidmatan melalui nama kelas sebagai id, yang berguna untuk menangani masalah bahawa antara muka dalam projek mungkin mempunyai pelaksanaan polimorfik dan memerlukan berbilang kejadian kelas dengan nama yang sama pada masa yang sama. ServiceIdentifier

此外,在构造 ServiceIdentifier 时,我们便可以将该类声明注入框架,而无需@injectable()显示调用了。

那么,这样一个 ServiceIdentifier 该如何构造呢?

// 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(&#39;@IServiceName-decorator can only be used to decorate a parameter&#39;);
    }
    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 需要变更多态实现,仅需修改 ServiceIdentifier 修饰符确定实现方式即可,无需更改业务的调用代码。

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

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

循环依赖问题

模块之间的依赖关系是有可能存在循环依赖的,比如 A 依赖 B,B 依赖 A。这种情况下进行两个模块的实例化会造成死循环,因此我们需要在框架中加入循环依赖检测机制来进行规避。

本质上,一个健康的模块依赖关系就是一个有向无环图(DAG),我们之前介绍过有向无环图在 excel 表格函数中的应用,放在依赖注入框架的设计中也同样适用。

我们可以通过深度优先搜索(DFS)来检测模块之间的依赖关系,如果发现存在循环依赖,则抛出异常。

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

该方法通过获取图节点的出度,将该类的全部依赖提取出来作为roots,然后逐个实例化,并从途中剥离该依赖节点。由于依赖树的构建是逐层依赖的,因此按顺序实例化即可。当发现该类的所有依赖都被实例化后,图中仍存在节点,则认为存在循环依赖,抛出异常。

总结

本篇文章简要介绍并实现了一个依赖注入框架,并解析了VSCode在实际问题上做出的一些改进。

实际上 VSCode 的依赖注入能力还有很多细节需要处理。例如异步实例化能力支持,通过封装 Deferred 类取得Promise执行状态,等等,在此就不一一展开了。感兴趣的同学可以参考 VSCode 源码:src/vs/platform/instantiation/common/instantiationService.ts,做更进一步的学习。

附录

最简 DI 框架完整 demo:github.com/realDuang/d…

更多关于VSCode的相关知识,请访问:vscode基础教程

Atas ialah kandungan terperinci Analisis terperinci suntikan pergantungan dalam VSCode. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam