VSCode의 종속성 주입에 대한 자세한 분석
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);
모듈 내부의 종속성 인스턴스화를 피하기 위해 외부에서 종속 개체를 주입하는 이 방법을 종속성 주입(DI)이라고 합니다. 이는 소프트웨어 엔지니어링의 일반적인 디자인 패턴이며 Java의 Spring, JS의 Angular 및 Node의 NestJS와 같은 프레임워크에서 이 디자인 패턴의 적용을 볼 수 있습니다.
물론 실제 애플리케이션에서는 모듈 수가 많고 종속성이 복잡하기 때문에 위의 예처럼 각 모듈의 인스턴스화 시점을 계획하고 모듈 인스턴스화 시퀀스를 작성하기가 어렵습니다. 더욱이 처음에 많은 모듈을 생성할 필요가 없으며 요청 시 인스턴스화해야 할 수도 있습니다. 따라서 대략적인 통합 인스턴스화는 권장되지 않습니다. 그래서 모든 모듈의 인스턴스화 프로세스를 분석하고 관리하기 위한 통합 프레임워크가 필요합니다. 이것이 종속성 주입 프레임워크의 역할입니다.
TypeScript의 데코레이터 기능을 통해 VSCode는 매우 가벼운 종속성 주입 프레임워크를 구현합니다. 먼저 이 독창적인 디자인의 미스터리를 풀기 위해 간략하게 구현해볼 수 있습니다.
가장 간단한 종속성 주입 프레임워크 설계
종속성 주입 프레임워크를 구현하려면 두 단계만 거치면 됩니다. 하나는 관리를 위해 모듈을 선언하고 프레임워크에 등록하는 것이고, 다른 하나는 모듈 생성자에서 필요한 모듈을 선언하는 것입니다. 의지하다.
먼저 TypeScript의 클래스 데코레이터 기능이 필요한 모듈 등록 프로세스를 살펴보겠습니다. 주입 시 모듈이 등록되었는지 여부만 확인하면 됩니다. 그렇지 않은 경우 모듈의 ID(여기서는 모듈 클래스 이름으로 단순화됨) 및 유형을 전달하면 단일 모듈 등록이 완료됩니다.
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); }; }
결국 프로젝트가 실행되기 전에 프레임워크 자체가 인스턴스화되었는지 확인하면 됩니다. (예제에서는 인젝터로 표시)
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의 인스턴스화 타이밍을 알 필요가 없습니다. 모듈을 직접 초기화하면 프레임워크가 자동으로 모듈 A와 B의 모든 종속성을 찾아 인스턴스화합니다. 당신 모듈.
VSCode의 종속성 수집 구현
위에서는 종속성 주입 프레임워크의 가장 간단한 구현을 소개합니다. 그런데 실제로 VSCode의 소스코드를 읽어보니 VSCode의 의존성 주입 프레임워크가 이런 방식으로 소비되지는 않는 것 같았습니다.
예를 들어 다음 인증 서비스에서 클래스에 클래스의 종속성 컬렉션으로 @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 ) {} }
사실 여기 수정자는 실제로 클래스 이름을 가리키는 것이 아니라 일반적으로 문자열이나 기호로 식별되는 동일한 이름의 리소스 설명자 ID(VSCode에서 ServiceIdentifier
라고 함)를 가리킵니다. . @injectable()
作为类的依赖收集,并且依赖服务也直接用其类名作为修饰器,而不是@inject()
。
// 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 装饰的类在运行时,将收集该类的依赖,注入到框架中。 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; } }
其实这里的修饰符并不是真正指向类名,而是一个同名的资源描述符 id(VSCode 中称之为 ServiceIdentifier
),通常使用字符串或 Symbol 标识。
通过 ServiceIdentifier
ServiceIdentifier
를 ID로 사용하면 프로젝트의 인터페이스가 다형성 구현을 가질 수 있고 여러 인스턴스가 필요할 수 있는 문제를 처리하는 데 도움이 됩니다. 동시에 같은 이름을 가진 클래스. 🎜此外,在构造 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('@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 需要变更多态实现,仅需修改 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基础教程!
위 내용은 VSCode의 종속성 주입에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











먼저 컴퓨터에서 vscode 소프트웨어를 열고 그림의 ①과 같이 왼쪽의 [확장] 아이콘을 클릭한 다음 그림의 ②와 같이 확장 인터페이스의 검색 상자에 [officeviewer]를 입력합니다. 그런 다음 검색 결과에서 [officeviewer]를 선택하여 그림의 ③과 같이 설치합니다. 마지막으로 아래와 같이 docx, pdf 등의 파일을 엽니다.

먼저 컴퓨터에서 Visual Studio 코드를 열고 왼쪽에 있는 네 개의 사각형 버튼을 클릭한 다음 검색 상자에 draw.io를 입력하여 플러그인을 쿼리하고 설치를 클릭한 후 새 test.drawio 파일을 생성합니다. test.drawio 파일을 선택하고 왼쪽의 편집 모드로 진입합니다. 측면에 다양한 그래픽이 있습니다. 드로잉 후 파일 → Embed → svg를 클릭한 다음 svg를 복사합니다. 복사한 svg 코드를 html 코드에 붙여넣으세요. html 웹페이지를 열면 해당 페이지의 그림을 클릭하시면 해당 페이지를 확대/축소하실 수 있습니다. 흐름도 여기서는 오른쪽 하단에 있는 연필 패턴을 클릭하여 웹 페이지로 이동합니다.

먼저, vscode 플러그인 관리자에서 Maude 플러그인을 검색할 수 있습니다. 그런 다음, maude의 코드 조각과 구문 강조를 사용하려면 확장명이 maude인 새 파일을 만듭니다. 터미널 -> 새 터미널은 현재 폴더에 있는 vscode 내장 터미널을 열어 maude 또는 full-maude 프로그램을 실행할 수 있습니다. Maude의 공식 튜토리얼에는 그림과 같이 호출하고 실행할 수 있는 http 클라이언트의 예도 있습니다. 파일을 fm 확장자와 연결하려면 설정을 열고 사용자 설정에서 파일 연결을 검색한 다음 settings.json을 엽니다. 파일 연결에 항목, 즉 *.fm에서 maude까지 항목을 추가하기만 하면 됩니다. 그러나 가득 차있다

테렌스 타오(Terence Tao)를 비롯한 많은 수학자들이 극찬한 공식 수학 도구인 LeanCopilot이 다시 진화했다고요? 방금 Caltech 교수인 Anima Anandkumar는 팀이 LeanCopilot 논문의 확장 버전을 출시하고 코드 기반을 업데이트했다고 발표했습니다. 이미지 논문 주소: https://arxiv.org/pdf/2404.12534.pdf 최신 실험에 따르면 이 Copilot 도구는 수학적 증명 단계의 80% 이상을 자동화할 수 있는 것으로 나타났습니다! 이 기록은 이전 베이스라인 이솝보다 2.3배 향상된 기록이다. 그리고 이전과 마찬가지로 MIT 라이선스에 따른 오픈 소스입니다. 사진 속 그는 중국 소년 송페이양이다.

1. 먼저 인터페이스를 연 후 왼쪽 상단에 있는 파일 메뉴를 클릭합니다. 2. 그런 다음 환경 설정 열에서 설정 버튼을 클릭합니다. 3. 그런 다음 이동하는 설정 페이지에서 업데이트 섹션을 찾습니다. 마지막으로 마우스를 클릭하여 확인하고 활성화합니다. Windows의 백그라운드에서 새 VSCode 버전 버튼을 다운로드하여 설치하고 프로그램을 다시 시작합니다.

1. 먼저 vscode 소프트웨어를 열고 탐색기 아이콘을 클릭한 후 작업 공간 창을 찾습니다. 2. 그런 다음 왼쪽 상단 모서리에 있는 파일 메뉴를 클릭하고 작업 공간에 폴더 추가 옵션을 찾습니다. 3. 마지막으로 폴더 위치를 찾습니다. 로컬 디스크, 추가 버튼을 클릭하세요

1. 먼저 설정 메뉴에서 설정 옵션을 엽니다. 2. 그런 다음 일반적으로 사용되는 페이지에서 터미널 열을 찾습니다. 3. 마지막으로 열 오른쪽에 있는 usewslprofiles 버튼을 선택 취소합니다.

1. 먼저 인터페이스를 연 후 작업 공간 인터페이스를 클릭합니다. 2. 그런 다음 열린 편집 패널에서 파일 메뉴를 클릭합니다. 3. 그런 다음 기본 설정 열 아래의 설정 버튼을 클릭합니다. 4. 마지막으로 마우스를 클릭하여 CursorSmoothCaretAnimation을 확인합니다. 버튼을 누르고 저장하면 됩니다.
