likes
comments
collection
share

组合模式 Composite

作者站长头像
站长
· 阅读数 3

组合模式 Composite

组合模式(Composite):将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

The Composite Pattern allows clients to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

组合模式 Composite

组合模式角色如下:

Component(组件): 定义了叶子和组合对象的公共接口,包括操作方法。它可以是抽象类或接口。

Leaf(叶子节点): 表示组合中的叶子节点对象,它实现了 Component 接口,但不包含子节点。

Composite(非叶子节点): 表示组合中的容器对象,它包含子节点,并实现了 Component 接口。Composite 可以包含 Leaf 和其他 Composite,形成递归结构。

⨳ 叶子节点(Leaf)和非叶子节点(Composite)被同一个规范(Comopent)限定;被装饰者(Concrete Component)和装饰者(Decorator)被同一个规范(Comopent)限定。

⨳ 非叶子节点(Composite)持有 Comopent 的引用,装饰者(Decorator)也持有 Component的引用。

不同的是,装饰者(Decorator)持有 Comopent 的引用,非叶子节点(Composite)持有 Comopent_s 的引用,这就意味着非叶子节点不能完成对树叶方法的增强(装饰),而仅仅是作为 一个存储 Comopent 的容器存在的。

被装饰者和装饰者都是 Comopent ,就可以让 Decorator 装饰 Decorator,让 Decorator 装饰 其他装饰着 DecoratorDecorator。。。你以为你看到的是一个装饰者,因为装饰者的嵌套,往里深看,发现里面连着一串的装饰者,看到头才发现那唯一一个被装饰者。

树叶和组合者都是 Comopent,就可以让 Composite 组合 Composite,让 Composite 组合 其他组合着 CompositeComposite。。。你以为你看到的是一个组合者,因为组合者的嵌套,往里深看,发现里面已经展成了一棵参天大树,顺着其中一条枝干看下去,才会发现挂在上面的几片树叶。

组合模式 Composite

下面看一下基本代码实现。

基本实现

组件 Component

Component 为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 的子部件。

public abstract class Component {

    protected String name;
    public Component(String name){
        this.name = name;
    }
    public abstract void add(Component component);
    public abstract void remove(Component component);
    public abstract void display(int depth);
}

叶子节点 Leaf

Leaf 在组合中表示叶节点对象,叶节点没有子节点。

public class Leaf extends Component {

    public Leaf(String name){
        super(name);
    }
    @Override
    public void add(Component component) {
        System.out.println("叶子节点不允许添加子节点");
    }

    @Override
    public void remove(Component component) {
        System.out.println("叶子节点没有子节点,无需删除");
    }

    @Override
    public void display(int depth) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<depth;i++){
            sb.append("-");
        }
        sb.append(name);
        System.out.println(sb.toString());
    }
}

非叶子节点 Composite

Composite 定义有枝节点行为,用来存储子部件,在 Component 接口中实现与子部件有关的操作,比如增加和删除。

public class Composite extends Component {

    private List<Component> children = new ArrayList<Component>();

    public Composite(String name){
        super(name);
    }
    @Override
    public void add(Component component) {
        children.add(component);
    }

    @Override
    public void remove(Component component) {
        children.remove(component);
    }

    @Override
    public void display(int depth) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<depth;i++){
            sb.append("-");
        }
        sb.append(name);
        System.out.println(sb.toString());
        for(Component c: children){
            c.display(depth+2);
        }
    }
}

你可能有这样的疑问:为什么 Leaf类 当中也有 addremove,树叶不是不可以再长分枝吗?

这种方式叫做透明方式,也就是说在 Component 中声明所有用来管理子对象的方法,其中包括 addremove 等。这样实现 Component 接口的所有子类都具备了 addremove 。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。

客户端 Client

// 生成树根 root
Component root = new Composite("root");
// 树根上长出两片叶子
root.add(new Leaf("Leaf A"));
root.add(new Leaf("Leaf B"));
// 树根上又长出分支 Composite X
Component comp = new Composite("Composite X");
comp.add(new Leaf("Leaf XA"));
comp.add(new Leaf("Leaf XB"));
root.add(comp);

// 分支 Composite X 又长出分支
Component comp2 = new Composite("Composite XY");
comp2.add(new Leaf("Leaf XYA"));
comp2.add(new Leaf("Leaf XYB"));
comp.add(comp2);

// 显示树状结构
root.display(1);

输出结果如下:

-root
---Leaf A
---Leaf B
---Composite X
-----Leaf XA
-----Leaf XB
-----Composite XY
-------Leaf XYA
-------Leaf XYB

源码赏析

Mybatis 之 SqlNode

在 MyBatis 中,SQL 映射文件(Mapper XML 文件)中的 SQL 语句被表示为一个树形结构,而 SqlNode 就是这个树形结构中的节点。

SqlNode 接口定义了 apply 方法,用于将节点中表示的 SQL 片段应用到 SQL 语句中。而 SqlNode 的具体实现类可以是叶子节点,也可以是包含其他节点的容器节点,这样就形成了一个树形结构。

叶子节点(Leaf Nodes):

StaticTextSqlNode:表示静态的 SQL 片段,如 SELECT * FROM table_name 中的 SELECT * FROM table_name

TextSqlNode:表示动态生成的 SQL 片段,例如带有动态参数的 SQL 片段,如 WHERE id = #{id} 中的 id = #{id}

非叶子节点(Composite Nodes):

IfSqlNode:表示带有条件判断的 SQL 片段,例如 <if test="condition">...</if> 中的 <if test="condition">...</if>

ChooseSqlNode:表示带有条件选择的 SQL 片段,类似于 Java 中的 switch 语句。

ForEachSqlNode:表示带有循环处理的 SQL 片段,用于处理集合或数组的循环操作,如 <foreach item="item" index="index" collection="list" open="(" separator="," close=")">...</foreach>

这些节点中,叶子节点通常表示单一的 SQL 片段,而非叶子节点则是包含其他 SqlNode 的容器,可以根据特定的条件或循环来组合和处理内部的 SQL 片段。通过这种组合方式,MyBatis 可以构建复杂的 SQL 查询语句,并且灵活地根据配置生成最终的 SQL 语句。

总结

当你需要表示对象的部分-整体层次结构时,例如树形菜单、文件系统等,组合模式就很有用了。它使得你可以统一处理叶子节点和容器节点。

优点如下:

操作的统一性:当你希望对对象和对象集合实施相同的操作时,组合模式可以简化代码。例如,在图形编辑器中,你可以对单个图形对象或图形对象组进行相同的操作(例如移动、旋转)。

简化客户端代码:客户端代码只需面对统一的接口,无需关心对象是单个对象还是对象组合。

可扩展性:通过组合模式,可以轻松地添加新的组合对象和叶子对象,而不会影响现有代码。

缺点如下:

叶子节点限制:叶子节点和组合节点的行为可能不同,如果在设计上没有考虑好这一点,可能会导致一些限制和困惑。

不容易限制类型:组合模式使得所有的节点都具有相同的接口,这意味着不容易限制某些类型的对象能够作为容器节点的子节点。

总的来说,组合模式适用于具有层次结构的场景,并且能够提供一种统一的方式来处理单个对象和对象组合。