组合模式实战运用(整体-部分,树形结构)

目录

前言

透明模式

安全模式

UML

plantuml

类图

实战代码

安全模式

优化


前言

组合模式将具有父子关系的对象组合成树形结构。

使用时需先抽象出节点的共有方法,提供统一的接口,使得客户端对叶子节点(无子节点)和组合节点(有子节点)的使用具有一致性。

提取共有方法,可分为两种方式

透明模式

接口除了定义了所有节点的共有方法,还定义了组合节点的独有方法,这么做的好处是使用时便可以依赖抽象,符合依赖倒置原则,但也同时违背了接口隔离原则,叶子节点可以使用组合节点的方法。

安全模式

接口只定义所有节点的共有方法,在使用节点对象时就必须指定具体的组合节点类或者叶子节点类,没法依赖抽象,违背依赖倒置原则,但是遵循了接口隔离原则。

UML

plantuml

@startuml
'https://plantuml.com/class-diagram

abstract Component {
    + operation() : void
    + add() : void
    + remove() : void;
    + getChildren() : List<Component>
}

class Composite {
    + children : List<Component
    + operation() : void
    + add() : void
    + remove() : void;
    + getChildren() : List<Component>
}

class Leaf {
    + operation() : void
}

class Client {}

Component <|-- Composite
Component <|-- Leaf

Composite "1" --> "n" Component

Client ..> Component

@enduml

类图

实战代码

安全模式

区分组合节点和叶子节点,就是要看节点下是否有子节点。

实际上,叶子节点和组合节点也可以有相同的子节点字段(children),只需通过子节点字段(children)是否为空数组就能区分组合节点还是叶子节点了。

所以将子节点字段(children)也一起抽象了 。

//抽象节点类
abstract class Node {

    private String id;
    private String pid;
    private List<Node> children;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }
}

//具体节点类
class ConcreteNode extends Node {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

public class TreeUtils {

    public final static String ROOT = "root";

    /**
     * 递归构造树
     *
     * @param sources 按parentId分类的节点
     * @param parentId 父id
     * @return 根节点集合
     */
    public static List<Node> buildTree(Map<String, List<Node>> sources, String parentId) {
        List<Node> nodes = sources.getOrDefault(parentId, emptyList());

        for (Node node : nodes) {
            List<Node> subNodes = buildTree(sources, node.getId());
            node.setChildren(subNodes);
        }

        return nodes;
    }

    //测试代码
    public static void main(String[] args) {
        ConcreteNode root = new ConcreteNode();
        root.setId("1");
        root.setPid(null);
        root.setName("root");

        ConcreteNode child1 = new ConcreteNode();
        child1.setId("2");
        child1.setPid("1");
        child1.setName("child1");

        ConcreteNode child2 = new ConcreteNode();
        child2.setId("3");
        child2.setPid("1");
        child2.setName("child2");

        List<Node> children = Arrays.asList(child1, child2);
        root.setChildren(children);
        //模拟从数据库中查到的节点数据
        List<Node> nodes = Arrays.asList(root, child1, child2);
        //按父节点分类
        Map<String, List<Node>> sources = nodes.stream()
                .collect(groupingBy(e -> Objects.isNull(e.getPid()) ? ROOT : e.getPid()));
        //构造树
        TreeUtils.buildTree(sources, ROOT);
    }

}

优化

实际使用中可直接简化成一个节点类

//节点实体类
public class Node {
    private String id;
    private String pid;
    private String name;
    private List<Node> children;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }
}

在开发过的项目中,一般都是直接构造完树后返回给前端做展示,需要的只是树的结构,组合节点和叶子节点除了基础的 get / set 方法没有其他方法。

既然无需关心节点的方法,组合节点和叶子节点的属性又都可以一致,就不需要专门抽象出一个统一的接口,只需要一个节点类就足够了。

使用时查询出所有节点,再通过通用工具类构造成树,最后返回前端。

有关通用工具类构造数方法:Java泛型+函数式接口实战:简单的通用构造树方法

  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值