如何将同一父类的两个子类视为相同但不同的类型



我需要帮助弄清楚如何在typescript中正确断言类型。

我正在使用的代码让rn感到困惑(我想我让它变得比需要的更复杂了,哈哈:3),所以我只给你我试图实现的目标的概述。

因此,基本上我有一个实现IAbstractFactory接口的ConcreteFactory类。ConcreteFactory类可以创建/生成3个不同的Concrete类BaseDocumentDocumentADocumentB

interface IAbstractFactory{
createBaseDocument(): Promise<IAbstractDocument>;
createDocumentA(): Promise<IAbstractDocument>;
createDocumentB(): Promise<IAbstractDocument>;
}
class ConcreteFactory implements IAbstractFactory{
public async createBaseDocument(): Promise<IAbstractDocument> {
return new BaseDocument();
}
public async createDocumentA(): Promise<IAbstractDocument> {
return new DocumentA();
}
public async createDocumentB(): Promise<IAbstractDocument> {
return new DocumentB();
}
}

请注意,只有Document类实现IAbstractDocument接口。DocumentA和DocumentB都扩展了BaseDocument

interface IAbstractDocument{
methodBase(){};
}
class BaseDocument implements IAbstractDocument{
methodBase(){};
}
// uses all of BaseDocuments functions but has some extra functions that DocumentB does not have
class DocumentA extends BaseDocument{
methodBase(){}; 
methodA(){}; // not implemented in IAbstractDocument, throws error
}
// uses all of BaseDocuments functions but has some extra functions that DocumentA does not have 
class DocumentB extends BaseDocument{
methodBase(){}; 
methodB(){}; // not implemented in IAbstractDocument, throws error
}

我还有一个名为Tree的类,它将构建一个Tree,每个节点的类型都是IAbstractDocument

class Tree{
rootNode:IAbstractDocument; // Realistically of type DocumentA or DocumentB, IAbstractDocument is throwing error, explained below

nodes: IAbstractDocument[]; // Realistically of type DocumentA or DocumentB, IAbstractDocument is throwing error, explained below
// this function will create factory and the factory will create DocumentA or Document B
createDocument(){}
addDocument()
}

我的目标是创建一个文档树。规则是DocumentA可以包含子节点,类型都是DocumentADocumentB,但

文档B问题是:

尽管DocumentADocumentB都扩展了BaseDocument。在Tree类中,它们是在类型IAbstractDocument下定义的(我这样做是因为我认为ConcreteFactory正在返回)。因此,现在我得到一个错误,DocumentADocumentB中的唯一函数(分别为方法A和方法B)没有在IAbstractDocument定义。所以我不能在Tree类中使用它们,我只能使用methodBase()

我的问题是:

如何将DocumentADocumentB视为相同但不同的类型它们都应该被视为中的文档节点,但它们有不同的相关功能。

任何帮助都将不胜感激,谢谢!

我认为您可能希望使用联合类型来表示"或者";,而不是超类或其他基类型,因为它们不容易检查。一般来说,您可能应该根据需要使用特定的类型,以便编译器知道您在做什么。

在接下来的内容中,我将名称DocumentA更改为DocumentCollectionDocumentB更改为DocumentSingle,因为我认为这就是您的意图。DocumentCollection将具有children属性,而DocumentSingle则不会。这是ConcreteFactory:

class ConcreteFactory {
public async createBaseDocument(): Promise<BaseDocument> {
return new BaseDocument();
}
public async createDocumentCollection(): Promise<DocumentCollection> {
return new DocumentCollection();
}
public async createDocumentSingle(): Promise<DocumentSingle> {
return new DocumentSingle();
}
}

查看它如何在返回签名中返回更具体的类型。现在让我们来看一下Document类的可能实现:

class BaseDocument {
methodBase() { };
constructor(public docName: string = "") { }
}
class DocumentCollection extends BaseDocument {
methodBase() { };
methodCollection() { console.log("MethodCollection by " + this.docName) };
children: Array<DocumentCollection> | Array<DocumentSingle> = [];
}
class DocumentSingle extends BaseDocument {
methodBase() { };
methodSingle() { console.log("MethodSingle by " + this.docName) };
}

我为文档提供了一个docName,这样我们以后可以看到一些有用的输出。注意DocumentCollectionchildren属性是Array<DocumentCollection>还是Array<DocumentSingle>。这将防止它成为一个混合的集合,如果我从你的问题中正确理解的话,你想禁止它。请注意,使用数组的并集比使用并集数组更难,但它就是这样。

让我们为DocumentSingleDocumentCollection的并集命名:

type Document = DocumentSingle | DocumentCollection;

如果你能帮助的话,你可能会想用Document而不是AbstractDocumentBaseDocument


现在让我们看看Tree:

class Tree {
nodes: Document[]
constructor(public rootNode: Document) {
this.nodes = [];
const addNodes = (x: Document): void => {
this.nodes.push(x)
if ("children" in x) {
x.children.forEach(addNodes)
}
}
addNodes(this.rootNode);
}
walk(
processSingle: (x: DocumentSingle) => void, 
processCollection: (x: DocumentCollection) => void
) {
const walk = (x: Document): void => {
if ("methodSingle" in x) {
processSingle(x);
} else {
processCollection(x);
x.children.forEach(walk)
}
}
walk(this.rootNode);
}
}

CCD_ 21具有CCD_ 23类型的CCD_。它还具有Document[]类型的nodes阵列。当您用rootNode构造Tree时,如果重要的话,构造函数还会遍历树来构建nodes数组。还有一个walk()方法,它对DocumentSingleDocumentCollection进行处理器回调,并遍历调用适当处理器的树。

注意,在addNodes()walk()箭头函数的实现中,编译器使用DocumentSingleDocumentCollection的特定属性之一的存在来将Document缩小到两个成员之一。这是因为in运算符可以用作类型保护。


所以让我们制作一个Tree:

const leafOne = new DocumentSingle("leafOne");
const leafTwo = new DocumentSingle("leafTwo");
const leafThree = new DocumentSingle("leafThree");
const leafFour = new DocumentSingle("leafFour");
const branchOne = new DocumentCollection("branchOne");
branchOne.children = [leafOne, leafTwo];
const branchTwo = new DocumentCollection("branchTwo");
branchTwo.children = [leafThree, leafFour];
const root = new DocumentCollection("root");
// note how I'm not allowed to assign a mixed thing to root.children
root.children = [branchOne, leafOne];
root.children = [branchOne, branchTwo]; // okay
const tree = new Tree(root);

从上面可以看出,在DocumentCollectionchildren属性中,不允许使用DocumentSingleDocumentCollection的混合数组。此外,您应该注意,我用一个新数组重新分配了children属性,而不是尝试将push()添加到它上。如果您尝试push(),您会发现它不允许您添加任何内容,因为它不知道数组是包含DocumentSingles还是DocumentCollections,但如果是这样的话,我们可以改变它⏳)

太好了,我们有一个tree。。。让我们walk()它:

tree.walk(x => x.methodSingle(), y => y.methodCollection());
/* [LOG]: "MethodCollection by root" 
[LOG]: "MethodCollection by branchOne" 
[LOG]: "MethodSingle by leafOne" 
[LOG]: "MethodSingle by leafTwo" 
[LOG]: "MethodCollection by branchTwo" 
[LOG]: "MethodSingle by leafThree" 
[LOG]: "MethodSingle by leafFour" */

太棒了!或者,我们可以迭代其nodes属性:

tree.nodes.forEach(x => "methodSingle" in x ? x.methodSingle() : x.methodCollection());
/* [LOG]: "MethodCollection by root" 
[LOG]: "MethodCollection by branchOne" 
[LOG]: "MethodSingle by leafOne" 
[LOG]: "MethodSingle by leafTwo" 
[LOG]: "MethodCollection by branchTwo" 
[LOG]: "MethodSingle by leafThree" 
[LOG]: "MethodSingle by leafFour" */

看起来不错。


到代码的游乐场链接

最新更新