关于生成树结构的一些小建议

559 阅读3分钟

树结构表示了层级关系,在一些目录或是管理层级中非常常用。本身树结构很简单,构建树的逻辑也不复杂,但是要优雅地使用还是需要一点点设计的。

树结构基本属性

首先我们建立树最基本的就是三个值:guidparentGuidchildren

guid用于定位节点,parentGuid定义节点关系,children存放层级数据。

定义接口

我们要生成树结构,那么每个节点就必然会涉及到上述的三个基本属性,所以我们规范出一个接口:

public interface Treeable<T> {

    String getGuid();

    String getParentGuid();

    List<T> getChildren();

    void setChildren(List<T> children);

}

其中的泛型T表示了节点类型,这样所有需要使用树结构的类只要去实现树接口,它就拥有了树节点的属性。为什么要规范结构而不是抽象类呢?我们后面再说。

树生成方法

有了树节点,我们就可以开始生成树结构了。

树的生成方法有很多,我这里介绍一个我常用的:

static <T extends Treeable<T>> List<T> buildTree(List<T> list) {
    Map<String, T> map = new HashMap<>();
    // 构造节点表
    for (T t : list) {
        map.put(t.getGuid(), t);
    }
    // 节点挂载
    List<T> root = new ArrayList<>();
    for (T t : list) {
        String parentGuid = t.getParentGuid();
        T parent = map.get(parentGuid);
        if (parent == null) {
            root.add(t);
        } else {
            List<T> children = parent.getChildren();
            if (children == null) {
                children = new ArrayList<>();
                parent.setChildren(children);
            }
            children.add(t);
        }
    }
    return root;
}

这个方法非常好理解,就是构建一个key为guid的表,方便我们拿节点。然后将有parentGuid的节点挂载到对应的节点children属性中,没有parentGuid的就是顶级节点。

我们再将所有的顶级节点收集到一个list中,这个list就是顶级目录列表了。

为了方便,我们可以把方法写在Treeable接口中,就像这样:

public interface Treeable<T> {

    String getGuid();

    String getParentGuid();

    List<T> getChildren();

    void setChildren(List<T> children);

    /**
     * 构建树(没有parentGuid的节点默认为父节点)
     *
     * @param list 原始列表
     * @return 树结构列表
     */
    static <T extends Treeable<T>> List<T> buildTree(List<T> list) {
        Map<String, T> map = new HashMap<>();
        // 构造表
        for (T t : list) {
            map.put(t.getGuid(), t);
        }
        List<T> root = new ArrayList<>();
        for (T t : list) {
            String parentGuid = t.getParentGuid();
            T parent = map.get(parentGuid);
            if (parent == null) {
                root.add(t);
            } else {
                List<T> children = parent.getChildren();
                if (children == null) {
                    children = new ArrayList<>();
                    parent.setChildren(children);
                }
                children.add(t);
            }
        }
        return root;
    }
}

使用

使用的话就非常简单了,第一步,定义树节点。假设我们有一个目录类Directory,我们就可以这样让它变成树节点:

public class DirectoryNode extends Directory implements Treeable<DirectoryNode>{

    private List<DirectoryNode> children;

    @Override
    public List<DirectoryNode> getChildren() {
        return children;
    }

    @Override
    public void setChildren(List<DirectoryNode> children) {
        this.children = children;
    }
}

这里没有实现getGuidgetParentGuid的原因是Directory类中就已经存在这两个方法了。

第二步,生成树结构:

List<DirectoryNode> DirectoryNodes = getAllDirectoryNodes();
return Treeable.buildTree(DirectoryNodes);

优点

为什么不直接抽象出一个抽象方法,将节点的三个属性直接封装呢?因为抽象方法只能被继承,而Java只支持单继承模式,所以如果我们使用继承的方式去描述节点,那么这个节点就无法再继承其他的父类,损失了拓展性。

这样的方式可以让节点属性随着原型类的变化而动态变化,不必维护两个属性列表。