如何将同一父类的两个子类视为相同但不同的类型?
Posted
技术标签:
【中文标题】如何将同一父类的两个子类视为相同但不同的类型?【英文标题】:How do I treat two children classes of the same parent class, as the same, yet different Type? 【发布时间】:2021-05-08 22:01:49 【问题描述】:我需要帮助来弄清楚如何在 typescript 中正确地断言类型。
我正在使用的代码让 rn 感到困惑(我认为我让它变得比它需要的更复杂,哈哈 :3),所以我将简要介绍一下我正在尝试完成的工作。
所以基本上我有一个实现 IAbstractFactory 接口的 ConcreteFactory 类。 ConcreteFactory 类可以创建/生成 3 个不同的具体类 BaseDocument、DocumentA 和 DocumentB。
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 的类,它将构建一棵树,每个节点的类型都是 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 可以包含子节点,类型为 DocumentA 或 DocumentB,但 DocumentB 不能有任何子节点(它们只能是叶节点)。
问题是:
虽然 DocumentA 和 DocumentB 都扩展了 BaseDocument。在 Tree 类中,它们在 IAbstractDocument 类型下定义(我这样做是因为我认为这就是 ConcreteFactory 返回的内容)。所以现在我得到一个错误,DocumentA 和 DocumentB(分别是methodA 和methodB)中的唯一函数没有在IAbstractDocument 中定义。所以我不能在 Tree 类中使用它们,我只能使用 methodBase()。
我的问题是:
如何将 DocumentA 和 DocumentB 视为相同但不同的类型?它们都应该被视为 中的文档节点树,但它们具有与它们相关的不同功能。
任何帮助将不胜感激,谢谢!
【问题讨论】:
请考虑修改这里的代码以构成一个minimal reproducible example 适合放入像The TypeScript Playground 这样的独立IDE,以便其他人可以重现您的问题(您所说的任何错误都应该是明显的) 并且只有您的问题(应删除其他错误,如拼写错误或未声明的依赖项)。如果我可以立即开始解决问题而不必先解决问题,那么提供帮助会容易得多。 我不知道在这里对Tree
说什么; nodes
不应该是其他 Tree
实例吗?如果您对子/叶区分有如此不同的要求,您可能希望Tree
本身是可子类化的,并具有DocumentATree
和DocumentBTree
。
我倾向于使用union types 并避免使用一般的基本/抽象版本。 this 是否为您工作或为您提供指导?如果是这样,我会写一个答案;如果不是,请详细说明您的问题。
对不起,如果我没有给出一个可重现的例子,我应该考虑一下。 Union 类型确实有意义,我认为这就是我需要的答案。谢谢!但是,让节点成为其他 Tree 实例是什么意思?将树拆分为多个子树对性能更好吗?我试图重新创建此树遍历算法bfs-dfs-example 的功能,但我想用DocumentA
和/或DocumentB
替换TreeNode
。
关于这两种类型的文档,我还想澄清一件事。对不起,如果我没有很好地解释它,但 DocumentA 充当其他“子集合”(DocA)或“子项”(DocB)的一种“集合”(DocA)。而 DocumentB 只是作为一个项目,它可以单独存在(根节点)或者是“集合/子集合”(DocA)的子节点。所以树结构的一个例子可以是 DocA -> [DocB, DocB] OR DocA -> [DocA, DocA] -> [[DocB, DocB], [DocB, DocB]] 或 DocB
【参考方案1】:
我认为您可能希望使用union types 来表示“非此即彼”,而不是超类或其他基类型,这些类型无法轻松检查。一般来说,您可能应该根据需要使用特定的类型,以使编译器知道您在做什么。
接下来我会将名称 DocumentA
更改为 DocumentCollection
并将 DocumentB
更改为 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
以便我们稍后可以看到一些有用的输出。注意DocumentCollection
的children
属性是Array<DocumentCollection>
还是Array<DocumentSingle>
。这将防止它成为一个混合集合,如果我从你的问题中理解正确,你想禁止它。请注意,使用联合数组比使用联合数组更难,但它就是这样。
让我们为DocumentSingle
和DocumentCollection
的联合命名:
type Document = DocumentSingle | DocumentCollection;
如果您能提供帮助,您可能希望使用Document
而不是AbstractDocument
或BaseDocument
。
现在让我们看看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);
Tree
有一个 rootNode
类型为 Document
。它还有一个nodes
类型为Document[]
的数组。当您使用其rootNode
构造Tree
时,构造函数还将遍历树以构建nodes
数组(如果重要)。还有一个walk()
方法,它为DocumentSingle
和DocumentCollection
获取处理器回调,并遍历调用适当处理器的树。
请注意,在addNodes()
和walk()
箭头函数的实现中,编译器使用DocumentSingle
或DocumentCollection
的特定属性之一的存在将Document
缩小为两名成员。这是因为the in
operator can be used as a type guard。
所以让我们创建一个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);
从上面可以看出,DocumentCollection
的children
属性中不允许使用DocumentSingle
和DocumentCollection
的混合数组。您还应该注意,我重新分配了 children
属性与一个新的数组,而不是尝试将 push()
放到它上面。如果您尝试使用push()
,您会发现它不会让您添加任何内容,因为它不知道数组是否包含DocumentSingle
s 或DocumentCollection
s。不确定这是否会破坏您的用例,但如果是这样,我们可以更改它。 (嗯,你可以改变它;我不知道我可以承诺它⏳)
太好了,我们有一个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" */
看起来不错。
Playground link to code
【讨论】:
以上是关于如何将同一父类的两个子类视为相同但不同的类型?的主要内容,如果未能解决你的问题,请参考以下文章