【发布时间】:2021-05-04 12:37:13
【问题描述】:
我有一个代表数据库中记录的类层次结构,每个表一个类。我希望能够正确键入检查类对象(表示表)以及类的实例(表示数据库中的各个行)。编译代码的唯一方法是通过将保存类的变量强制转换为<any> 来禁用类型检查。下面是一个人为的例子,展示了我遇到的问题。
我的主要问题是关于持有类的变量的类型。我认为这三个问题的答案应该是相同的:
- 我应该为
name字典(在代码的第 3 行定义)中的值赋予什么类型? - 我应该给
cls(定义在第31行)什么类型? - 同样,第 49 行的函数
maybeMakeInstance中的cls应该是什么类型
额外问题:
- 有没有办法确保静态成员(例如,在第 3 行定义的
names)永远不会在子类中定义? - 声明必须在子类中重新定义的静态成员(例如,
tablename和all在第 6 行和第 7 行定义并在第 61 和 62 行重新定义)的正确方法是什么?
class A {
// this should not be inheritable
static names:{ [name: string]: { new(key:string):A} } = {};
// next two need to be inherited in each subclass
static tablename:string = "Aclass";
static all: Record<string, A> = {};
id: string;
// create a new object and put into per class cache, `all`
constructor(key:string = null) {
this.id = key;
if (key != null) {
new.target.all[key] = this;
}
}
// return instance matching `key`. Will only search in exact class (not subclasses or superclasses)
static find(key:string):A|null {
if (key in this.all) {
return this.all[key];
}
console.log(`${key} not in ${this.tablename}`);
return null;
}
// pretty print info about instance
show():void {
// What is proper type for `this.constructor`? <{ new(key:string):A}> fails as does doing nothing.
const cls = <any>this.constructor;
console.log(`${this.id} from ${cls.tablename}`);
}
// pretty print info about instance
static showall():void {
for (let x in this.all) {
this.all[x].show();
}
}
static init(name:string):void {
this.names[name] = this;
}
static maybeMakeInstance(clsname:string, key:string):A {
if ( !(clsname in A.names) ) throw new Error(`unknown classname: ${clsname}`);
// what is proper type of `cls`?
let cls:any = A.names[clsname];
if (key in cls.all) {
console.log(`Reusing ${key} in class ${clsname}/${cls.tablename}`);
return cls.all[key];
}
return new cls(key);
}
};
A.init('classA');
class B extends A {
// is this proper way to override superclass static members?
static tablename:string = "Bclass";
static all: Record<string, B> = {};
}
B.init('classB');
// make sure above code is working.
function main() {
let a = new A('first');
A.showall();
A.find('first').show();
new A('second');
new B('one');
A.showall();
B.showall();
console.log(B.find('first'));
console.log(B.find('second'));
console.log(B.find('one'));
console.log(A.find('one'));
A.maybeMakeInstance('classA', 'third');
A.maybeMakeInstance('classB', 'two');
A.maybeMakeInstance('classB', 'two');
console.log('------ A');
A.showall();
console.log('------ B');
B.showall();
A.maybeMakeInstance('classA', 'two');
console.log('------ A');
A.showall();
}
main();
////////////////
// running this file will result in the following output:
////////////////
// first from Aclass
// first from Aclass
// first from Aclass
// second from Aclass
// one from Bclass
// first not in Bclass
// null
// second not in Bclass
// null
// B { id: 'one' }
// one not in Aclass
// null
// Reusing two in class classB/Bclass
// ------ A
// first from Aclass
// second from Aclass
// third from Aclass
// ------ B
// one from Bclass
// two from Bclass
// ------ A
// first from Aclass
// second from Aclass
// third from Aclass
// two from Aclass
////////////////
【问题讨论】:
-
您在这里尝试做的一些事情,比如要求在子类中覆盖静态成员,有点违背 OOP 设计。您可能需要考虑重组它以使用组合而不是继承。
-
忽略奖励问题(答案基本上是“否”,因为 TS 目前不支持
abstract static成员,并且可能永远不会支持final成员),你也许可以逃脱只是typeof A。这里的问题是子类构造函数没有形成正确的类型层次结构。所以class B extends A不保证typeof B扩展typeof A。因此this.constructor的类型太宽了,Function。如果typeof A适用于您的用例,那就太好了。如果没有,请说明失败的地方。 -
请参阅this question 并让我知道它的答案是否足以解决这里发生的事情;如果没有,请让我知道,具体来说,仍然需要回答什么。祝你好运!
标签: typescript class types