简体   繁体   English

如何使用注入的功能实现结构的通用层次结构

[英]How to implement a generic hierarchy of structures with injected functionality

I want to implement a generic hierarchy for tree structures, which can later be used in an implementation-independent way to describe generic algorithms over trees. 我想为树结构实现一个通用层次结构,以后可以用一种与实现无关的方式来描述树上的通用算法。

I started with this hierarchy: 我从这个层次结构开始:

interface BinaryTree<Node> {
    Node left(Node);
    bool hasLeft(Node);

    Node right(Node);
    bool hasRight(Node);
}

interface BinaryTreeWithRoot<Node> : BinaryTree<Node> {
    Node root();
}

interface BinaryTreeWithParent<Node> : BinaryTree<Node> {
    Node parent(Node);
    bool hasParent(Node);
}

Now, basically I want to be able to implement the concept of a subtree in a universal way: For each class T : BinaryTree, I want a 'class' Subtree(T) which provides the same functionality of T (so it must derive from it), and also rewrites the root() functionality. 现在,基本上我希望能够以通用的方式实现子树的概念:对于每个类T:BinaryTree,我想要一个'类'子树(T),它提供相同的T功能(所以它必须来自它),并重写root()功能。

Something like this: 像这样的东西:

class Subtree<T, Node> : T, BinaryTreeWithRoot<Node> 
    where T : BinaryTree<Node>
{
    T reference;
    Node root;

    void setRoot(Node root) {
        this.root = root;
    }

    override Node BinaryTreeWithRoot<Node>::root() {
        return this.root;
    }

    // Now, inherit all the functionality of T, so an instance of this class can be used anywhere where T can.
    forall method(arguments) return reference.method(arguments);
}

Now with this code I'm not sure how to create an object of type subtree, since the tree object should in some way be injected. 现在有了这段代码,我不知道如何创建一个类型子树的对象,因为树对象应该以某种方式注入。

One approach is to create a subtree class for each tree class i create, but this means code duplication, and, after all, its the same thing. 一种方法是为我创建的每个树类创建一个子树类,但这意味着代码重复,毕竟它是同一个东西。

So, one approach to this is mixins, which allow a generic class to derive from its template parameter. 因此,一种方法是mixins,它允许泛型类从其模板参数派生。

I'm also interested how such a hierarchy can be implemented in Haskell, since Haskell has a great type system and I think it will be easier to inject such functionality. 我也很感兴趣如何在Haskell中实现这样的层次结构,因为Haskell有一个很好的类型系统,我认为注入这样的功能会更容易。

For example in Haskell it may be something like: 例如在Haskell中它可能是这样的:

class BinaryTree tree node where
    left :: tree -> node -> node
    right :: tree -> node -> node

class BinaryTreeWithRoot node where
    left :: tree -> node -> node
    right :: tree -> node -> node -- but this is a duplication of the code of BinaryTree
    root :: tree -> node

instance BinaryTree (BinaryTreeWithRoot node) where
    left = left
    right = right

data (BinaryTree tree node) => Subtree tree node = 
 ...

instance BinaryTreeWithRoot (Subtree tree node) where ...

I'm interested if and how this can be done in within an oop language (c++,c#,d,java), as c++ and d provide mixins out of the box (and i'm not sure for d), and out of curiosity with the Haskell type system. 我很感兴趣是否以及如何在oop语言(c ++,c#,d,java)中完成这项工作,因为c ++和d提供了开箱即用的mixins(我不确定d),并且对Haskell类型系统的好奇心。

Since D has "real" templates, not generics, making a template class inherit from its template parameter is trivial: 由于D具有“真实”模板,而不是泛型,因此使模板类从其模板参数继承是微不足道的:

class A {}
class B(T) : T {
    static assert(is(B!T : T));  // Passes.
}

As far as making Subtree work in D, something like this should do it, assuming you also have a template class Node : 至于使Subtree在D中工作,这样的事情应该这样做,假设你还有一个模板类Node

class Subtree(T) : T, BinaryTreeWithRoot!(Node!(T))
{
    T reference;
    Node root;

    void setRoot(Node root) {
        this.root = root;
    }

    override Node root() {
        return this.root;
    }
}

However, IIUC (correct me if I'm wrong), T is the payload of the tree and could therefore be a primitive. 但是,IIUC(如果我错了,请纠正我), T是树的有效载荷,因此可能是原始的。 If that's the case, you would be better off getting your ability to use a Subtree!(T) as a T via alias this , which allows for subtyping without inheritance and works with primitives: 如果是这种情况,你最好能够使用子Subtree!(T)作为通过alias thisT ,这允许在没有继承的情况下进行子类型化并使用原语:

class Subtree(T) : BinaryTreeWithRoot!(Node!(T))
{
    T reference;
    alias reference this;  // Make this implicitly convertible to reference.
    Node root;

    void setRoot(Node root) {
        this.root = root;
    }

    override Node root() {
        return this.root;
    }
}

Creating a tree interface like this in Haskell is ... unusual. 在Haskell中创建这样的树接口是不寻常的。 Both Node and Subtree are superfluous. NodeSubtree都是多余的。 This is partially due to algebraic types, and partially because Haskell data is immutable so different techniques are required to accomplish certain things (like setting the root node). 这部分是由于代数类型,部分是因为Haskell数据是不可变的,因此需要不同的技术来完成某些事情(比如设置根节点)。 It is possible to do it, the interface would look something like: 可以这样做,界面看起来像:

class BinaryTree tree where
    left :: tree a -> Maybe (tree a)
    right :: tree a -> Maybe (tree a)

-- BinaryTreeWithRoot inherits the BinaryTree interface
class BinaryTree tree => BinaryTreeWithRoot tree where
    root :: tree a -> tree a

Then, with a pretty standard binary tree definition: 然后,使用非常标准的二叉树定义:

data Tree a =
  Leaf
  | Branch a (Tree a) (Tree a)

instance BinaryTree Tree where
  left Leaf = Nothing
  left (Branch _ l r) = Just l
  right Leaf = Nothing
  right (Branch _ l r) = Just r

data TreeWithRoot a =
  LeafR (TreeWithRoot a)
  | BranchR a (TreeWithRoot a) (TreeWithRoot a) (TreeWithRoot a)

instance BinaryTree TreeWithRoot where
-- BinaryTree definitions omitted

instance BinaryTreeWithRoot TreeWithRoot where
  root (LeafR rt) = rt
  root (BranchR _ rt l r) = rt

Since this interface returns a Maybe (tree a) , you can also use left and right to check if the branches exist instead of using separate methods. 由于此接口返回Maybe (tree a) ,因此您还可以使用leftright来检查分支是否存在而不是使用单独的方法。

There's nothing particularly wrong with it, but I don't believe I've ever seen anyone actually implement this approach. 这没什么特别的错,但我不相信我见过有人真正实现过这种方法。 The more usual techniques are either to define traversals in terms of Foldable and Traversable or create a zipper . 更常用的技术是根据Foldable和可Traversable定义遍历或创建拉链 Zippers are simple to derive manually, but there are several generic zipper implementations, such as zipper , pez , and syz . 拉链很容易手动派生,但有几种通用拉链实现,如zipperpezsyz

In C# 4 I would use dynamics for achieving this goal. 在C#4中,我将使用动力学来实现这一目标。 For example you could try defining the SubtTree class as: 例如,您可以尝试将SubtTree类定义为:

public class Subtree<T, Node> : DynamicObject, BinaryTreeWithRoot<Node> where T : BinaryTree<Node>
{
    private readonly T tree;

    public Subtree(T tree)
    {
        this.tree = tree;
    }
}

and override appropriate methods of DynamicObject using methods/properties of tree. 并使用树的方法/属性覆盖DynamicObject的适当方法。 More information (and sample code) can be found in this great blog post about Using C# 4.0 dynamic to drastically simplify your private reflection code . 更多信息(和示例代码)可以在这篇关于使用C#4.0动态的博客文章中找到, 以大大简化您的私有反射代码

It is worth mentioning that due to usage of dynamic capabilities and reflection, small performance overhead will be introduced as well as reduced safety (as it may involve violation of encapsulation). 值得一提的是,由于动态功能和反射的使用,将引入较小的性能开销以及降低安全性(因为它可能涉及违反封装)。

As you pointed out, one approach is to create a subtree class for each tree class i create, This means code duplication, but it can be somehow "avoided", or better, automated, using reflection and T4. 正如您所指出的,一种方法是为我创建的每个树类创建一个子树类,这意味着代码重复,但它可以使用反射和T4以某种方式“避免”或更好地自动化。 I did it myself for a past project and it works quite well! 我自己做了一个过去的项目,它运作得很好!

You may start from Oleg Synch blog for an overview on T4. 您可以从Oleg Synch博客开始,了解T4的概述。 Here a good example of automatically generated classes: http://www.olegsych.com/2007/12/how-to-use-t4-to-generate-decorator-classes/ 这是自动生成的类的一个很好的例子: http//www.olegsych.com/2007/12/how-to-use-t4-to-generate-decorator-classes/

I think the approach through "BinaryTree"s assumes too much of a fixed structure and unnecessarily defines your interface in a nongeneric way. 我认为通过“BinaryTree”的方法假设过多的固定结构并且以非通用方式不必要地定义您的接口。 Doing this makes it difficult to reuse algorithms as your tree expands into non-binary forms. 这样做会使您在树扩展为非二进制形式时难以重用算法。 Instead, you will need to code your interfaces for multiple styles when such is not necessary or useful. 相反,如果没有必要或有用,您将需要为多种样式编写接口代码。

Also, coding with hasLeft/hasRight checks means every access is a two step process. 此外,使用hasLeft / hasRight检查进行编码意味着每次访问都是一个两步过程。 Checking existence of a fixed position will not provide for efficient algorithms. 检查固定位置的存在将不提供有效的算法。 Instead, I think you will find that adding a generic property that may be binary left/right or binary red/black or a character index or anything else will allow much more reuse of your algorithms and checking that data can be done only by those algorithms that need it (specific binary algorithms). 相反,我认为您会发现添加可能是二进制左/右或二进制红/黑或字符索引或其他任何内容的通用属性将允许更多地重用您的算法并检查数据只能由这些算法完成需要它(特定的二进制算法)。

From a semantic view, you want to encode some basic properties first, and then specialise. 从语义视图中,您希望首先编码一些基本属性,然后进行专门化。 When you are "at" a node inside an algorithm, you want to be able to first find the children edges . 当您“处于”算法内的节点时,您希望能够首先找到子 This should be a container range of edge structures that allow you to navigate to the children nodes. 这应该是容器范围的边缘结构,允许您导航到子节点。 Since it can be a generic container, it could have 0, 2, 5, 1, or even a 100 edges in it. 由于它可以是通用容器,因此它可以包含0,2,5,1或甚至100个边。 Many algorithms do not care. 许多算法都不关心。 If it has 0, iterating over the range will do nothing - no need to check hasX or hasY. 如果它为0,则迭代范围将不执行任何操作 - 无需检查hasX或hasY。 For each edge, you should be able to get the node of the child, and recurse for whatever algorithm you wish. 对于每个边缘,您应该能够获得子节点,并递归所需的算法。

This is basically the approach boost takes in it's Graph library, and it allows for expansion of tree algorithms to graphs where they are applicable, for even better generic algorithm reuse. 这基本上是它的图形库中的方法提升,它允许将树算法扩展到适用的图形,以实现更好的通用算法重用。

So already you have a basic interface with this 所以你已经有了一个基本的界面

TreeNode:
  getChildEdges: () -> TreeEdgeRange

TreeEdge:
  getChildNode: () -> TreeNode

and whatever range-to-object your favorite language enjoys. 以及您最喜欢的语言所享有的任何范围到对象。 D has a particularly useful range syntax, for instance. 例如,D具有特别有用的范围语法。

You will want to have some basic Tree object that gives you nodes. 您将需要一些基本的Tree对象来为您提供节点。 Something like 就像是

Tree:
  getTreeNodes: () -> TreeNodeRange

starts you out. 开始你。

Now, if you want to support BinaryTrees, do so as a restriction on this interface. 现在,如果您想支持BinaryTrees,请将此作为对此接口的限制。 Note that you don't really need any new interface methods, you just need to enforce more invariants - that every TreeNode has 0, 1, or 2 childEdges. 请注意,您实际上并不需要任何新的接口方法,只需要强制执行更多不变量 - 每个TreeNode都有0,1或2个childEdges。 Just make an interface type that indicates this semantic restriction: 只需创建一个指示此语义限制的接口类型:

BinaryTree : Tree

And if you want to support rooted trees, adding an interface layer with 如果你想支持有根树,添加一个接口层

RootedTree : Tree:
  getRoot: () -> TreeNode

adds that capability. 增加了这种能力。

The basic idea is that you shouldn't have to add interface methods to add semantic requirements if you are making your classes more specific down the hierarchy. 基本思想是,如果要使类在层次结构中更具体,则不必添加接口方法来添加语义要求。 Only add interface methods if there is a new semantic behavior that needs accessed. 如果存在需要访问的新语义行为,则仅添加接口方法。 Otherwise - enforce new invariants on the generic interface. 否则 - 在通用接口上强制执行新的不变量。

Eventually, you'll want to decorate nodes and edges with structures that hold data about the node or edge, so you can build Tries and Red-Black trees and all the great tools of advanced algorithmics. 最终,您需要使用包含节点或边缘数据的结构来装饰节点和边缘,这样您就可以构建Tries和Red-Black树以及高级算法的所有优秀工具。 So you will want to have 所以你会想拥有

PropertiedTreeNode<Property> : TreeNode:
  getProperty: () -> Property

PropertiedTreeEdge<Property> : TreeEdge:
  getProperty: () -> Property

Since this is something you will want to allow generic algorithms to work on, the type information of whether a property is a part of the Tree or not should be generic and something algorithms can ignore. 由于这是您希望允许通用算法工作的内容,因此属性是否是Tree的一部分的类型信息应该是通用的,并且算法可以忽略。 This puts you on the design track of boost, where these issues have been resolved very elegantly. 这将使您进入boost的设计轨道,这些问题已得到非常优雅的解决。 I would recommend studying that library if you want ideas on how to build a generic tree algorithm library. 如果您想了解如何构建通用树算法库的想法,我建议您研究该库。

If you follow the above guidelines of types-equating-to-semantic-descriptions, then thee SubTree should be obvious - it's exactly the same type as the tree it is coming from! 如果你遵循上面的类型等同于语义描述的指导原则,那么SubTree应该是显而易见的 - 它与它来自的树完全相同! In fact, you really should not have a SubTree type at all. 实际上,你真的不应该有SubTree类型。 Instead, you should just have a method of the specific TreeNode type you are dealing with 相反,您应该只有一个您正在处理的特定TreeNode类型的方法

PropertiedTreeNode<Property>:
  getSubTree: () -> PropertiedTree<Property>

And, as in boost, as you encode more of the information of the capabilities of Tree in it's generic properties, you can get new Tree types with broader interface contracts. 而且,在boost中,当您在其通用属性中编码Tree的更多功能信息时,您可以获得具有更广泛接口契约的新Tree类型。

You could do:- 你可以这样做: -

public class Node {
    public Node Left {get; set:}
    public Node Right {get; set;}
    public Node Parent {get; set;}  // if you want to be able to go up the tree
    public Node Root {get; set;}    // only if you want a direct link to root
}

A sub-tree is just a tree, and every tree can be represented as the root node of that tree and then that node can have properties enabling navigation through the tree. 子树只是一棵树,每棵树都可以表示为该树的根节点,然后该节点可以具有启用树导航的属性。

Make this a generic Node<T> and store the value too. 使其成为通用Node<T>并存储该值。 If you don't like public setters make them private and set them only in a constructor or some safe AddLeft(...) , etc. methods. 如果您不喜欢公共setter,请将它们设为私有,并仅在构造函数或某些安全的AddLeft(...)等方法中设置它们。

You can get rid of Root too and just traverse up the Parent links until you find a null Parent value (or reach the top node for your subtree case). 您也可以删除Root ,只需遍历Parent链接,直到找到null Parent值(或到达子树案例的顶部节点)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM