typescript - Typescript - How to correctly achieve code decoupling?
问题描述
My little project is setup with vanilla Typescript and Webpack to build and bundle everything in a single output script.
What i'm trying to achieve is a Factory
class that would manage the instantiation of some objects for my main application, i also wish to add a new class that the Factory would be capable of managing without ever having to modify the Factory
itself.
For this purpose since Typescript doesn't support reflection to get all implementations of an Interface i have followed the approach suggested in the accepted answer to this question, through annotations and a registry: Typescript - Get all implementations of interface
Which seemed to work fine at first, but now since the modules containing the classes aren't imported anywhere, thus seemingly "unlinked" from the application's entry point, tsc
's Tree-Shaking actually prunes these modules from the build process and the code doesn't make it into the final output script.
I have read about Annotations breaking Tree-Shaking in Typescript, but this time it looks like it's working fine
This issue reminds me of Components
in Angular
which have to be manually registered in the application module after declaration
The last thing i've tried was to group all the classes that need to be registered and the Registry in a Namespace
, this did work as everything in the namespace was built and made it in the output script, at the expense of having everything in a single File, since from what i have tried and what i understand Multifile namespaces don't really work well in Typescript
Reference: Typescriptlang Namespaces DOC and: How do I use namespaces with TypeScript external modules?
So ultimately, is there something conceptually wrong in my approach? Should i compromise with building a registry of all the modules manually? Or Reference them all directly in my Factory and update it everytime i add a new module?
EDIT 1: I will post an example as requested by @captain-yossarian, the code is almost identical to the first linked question so i thought this would have been superfluous
ITest.ts
export interface ITest{}
TestBase.ts
export class TestBase implements ITest{}
TeastA.ts
import { TestBase } from "TestBase";
import { register } from "TestRegistry";
@register("A")
export class TestA extends TestBase{
constructor(){
super()
console.log("Hi, i'm TEST A")
}
}
TestB.ts and TestC.ts are identical to A, changing just the register and output letter
TestRegistry.ts
import { TestBase } from "TestBase"
export type Constructor<T> = {
new(): T
readonly prototype: T
}
export const registry: Map<string, Constructor<TestBase>> = new Map<string, Constructor<TestBase>> ()
export function register(id: string){
return function<T extends Constructor<TestBase>>(test: T) {
registry.set(id, test)
}
}
TestNamespace.ts (contains everything seen above in a separate namespace)
export namespace TestNamespace{
export interface ITest{}
export class TestBase implements ITest{}
@register("A")
export class TestA extends TestBase{
constructor(){
super()
console.log("Hi, i'm TEST A")
}
}
@register("B")
export class TestB extends TestBase{
constructor(){
super()
console.log("Hi, i'm TEST B")
}
}
@register("C")
export class TestC extends TestBase{
constructor(){
super()
console.log("Hi, i'm TEST C")
}
}
export type Constructor<T> = {
new(): T
readonly prototype: T
}
export const registry: Map<string, Constructor<TestBase>> = new Map<string, Constructor<TestBase>> ()
export function register(id: string){
return function<T extends Constructor<TestBase>>(test: T) {
registry.set(id, test)
}
}
}
TestFactory.ts
import { ITest } from "ITest"
import { TestBase } from "TestBase"
import { TestNamespace } from "TestNamespace"
import { Constructor, registry } from "TestRegistry"
export class TestFactory{
public constructor(){
}
public getTestInstance(id: string): ITest{
let testConstructor: Constructor<TestBase> = registry.get(id)
let test: ITest = new testConstructor() || new TestBase()
return test
}
public getTestInstanceFromNamespace(id: string) : TestNamespace.ITest{
let testConstructor: Constructor<TestNamespace.TestBase> = TestNamespace.registry.get(id)
let test: TestNamespace.ITest = new testConstructor() || new TestNamespace.TestBase()
return test
}
}
TestScript.ts
import {TestFactory} from "TestFactory"
var testFactory = new TestFactory();
testFactory.getTestInstance("A") // When compiled and run this should return errors for TestFactory
testFactory.getTestInstance("B") // line 12 "testConstructor is not a constructor" as the registry will be empty
testFactory.getTestInstance("C") // the code for TestA, TestB and TestC is not built
testFactory.getTestInstanceFromNamespace("A")//Output should be: "Hi, i'm TEST A"
testFactory.getTestInstanceFromNamespace("B")//Hi, i'm TEST B
testFactory.getTestInstanceFromNamespace("C")//Hi, i'm TEST C
//The second approach should work because once referenced the whole Namespace is built into the final output file
If the provided code is too much i could make a sample project on github
解决方案
推荐阅读
- r - 为什么我的图表上的错误栏不合适?
- notepad++ - 如何在记事本中按增量顺序列出“壁纸/原始/1.jpg”行?
- go - 有没有一种断言类型的通用方法?
- javascript - 我可以将特定数据添加到对象的属性吗?
- java - 按流程生成 camunda 报告,而不是按测试用例
- javascript - 使用Vuejs传入对象格式化日期输入框
- javascript - 如果包含在数组中,从字符串中删除单词?
- javascript - JavaScript 代理:如何清空数组目标
- javascript - 如何使用“输入颜色和 onchange”更改画布背景颜色
- algorithm - 模式查找算法