查看原文
其他

设计模式系列4 - 组合模式

吕梦楼 楼仔 2022-10-28

主要讲解组合模式和实际应用的场景,基于Java。

前言

最初接触组合模式,是来源于我们小组同学的一个分享,他当时给我们讲购物车界面的重构,把之前的购物车大单体拆成了树状结构,就用到了组合模式。

我们看一下组合模式的定义:

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

组合模式其实比较简单,层次很分明,主要包括一个抽象接口、组合对象节点和叶子节点:

  • Component抽象组件:为组合中的所有对象提供一个接口,不管是叶子对象还是组合对象。
  • Component组合节点对象:实现了接口的所有操作,并且持有子节点对象。
  • Leaf叶子节点对象:叶子节点没有任何子节点,实现了接口中的某些操作。

前面讲的有点啰嗦,理解一个设计模式,最快就是看示例,如果大家不想看文字,也可以直接跳到代码示例部分。

组合模式

对组合模式的理解,其实就是一个“总-分”的关系,直接先看定义的抽象类:

public abstract class penguin {
    protected String name;

    public penguin(String name) {
        this.name = name;
    }

    public abstract void beating();

    public void add(penguin p) {
        throw new UnsupportedOperationException();
    }
    public void remove(penguin p) {
        throw new UnsupportedOperationException();
    }
    public penguin getChild(int i) {
        throw new UnsupportedOperationException();
    }
    public List<penguin> getChilds() {
        throw new UnsupportedOperationException();
    }
}

这个抽象类其实就是定义了一个公共的行为beating,然后增加了一些方法,这些方法在“Component组合节点对象”都需要实现,但是在“Leaf叶子节点对象”可以不用实现。我这里其实想先丢弃叶子节点,讲一个简洁版的组合模式。下面看“Component组合节点对象”的代码:

public class batchPenguin extends penguin {
    private List<penguin> m_penguins = new ArrayList<>();
    public batchPenguin(String name) {
        super(name);
    }
    @Override
    public void beating() {
        System.out.println(this.name + "打豆豆");
        for (penguin p : m_penguins) {
            p.beating();
        }
    }
    @Override
    public void add(penguin p) {
        m_penguins.add(p);
    }
    @Override
    public void remove(penguin p) {
        m_penguins.remove(p);
    }
    @Override
    public penguin getChild(int i) {
        return m_penguins.get(i);
    }
    @Override
    public List<penguin> getChilds() {
        return m_penguins;
    }
}

所有的对象,都有一个打豆豆的行为,然后每个对象都有增加、删除、获取子节点的方法,这个其实就是组合模式的核心,最后是使用姿势:

public static void main(String[] args) {
    batchPenguin grandFatherPenguin = new batchPenguin("grandFatherPenguin");
    batchPenguin fatherPenguin = new batchPenguin("fatherPenguin");
    batchPenguin motherPenguin = new batchPenguin("motherPenguin");
    batchPenguin childPenguin1 = new batchPenguin("childPenguin1");
    batchPenguin childPenguin2 = new batchPenguin("childPenguin2");
    batchPenguin childPenguin3 = new batchPenguin("childPenguin3");
    batchPenguin childPenguin4 = new batchPenguin("childPenguin4");
    fatherPenguin.add(childPenguin1);
    fatherPenguin.add(childPenguin2);
    motherPenguin.add(childPenguin3);
    motherPenguin.add(childPenguin4);
    grandFatherPenguin.add(fatherPenguin);
    grandFatherPenguin.add(motherPenguin);
    grandFatherPenguin.beating();
}

输出结果:

grandFatherPenguin打豆豆
fatherPenguin打豆豆
childPenguin1打豆豆
childPenguin2打豆豆
motherPenguin打豆豆
childPenguin3打豆豆
childPenguin4打豆豆

我们可以看到组合模式的好处,就是我们不用关系每个对象里面的子成员,只要我们把子对象add()进去后,调用父节点的beating()操作后,会执行子成员,以及子成员包括下面所有子节点的beating()操作,形成一个递归操作。

加上叶子节点

我上面的示例,没有加上叶子节点,其实我在实际的应用场景中,没有使用叶子节点,直接就是这个简版的组合模式,也能达到我想要的效果。不过既然组合模式有叶子节点,我也就加上,仅作为了解使用,下面是叶子节点代码:

public class leaf extends penguin{
    public leaf(String name) {
        super(name);
    }
    @Override
    public void beating() {
        System.out.println(name + "打豆豆");
    }
}

然后在main中新增leaf节点:

leaf leaf1 = new leaf("leaf1");
leaf leaf2 = new leaf("leaf2");
leaf leaf3 = new leaf("leaf3");
leaf leaf4 = new leaf("leaf4");
childPenguin1.add(leaf1);
childPenguin2.add(leaf2);
childPenguin3.add(leaf3);
childPenguin4.add(leaf4);

最后输出:

grandFatherPenguin打豆豆
fatherPenguin打豆豆
childPenguin1打豆豆
leaf1打豆豆
childPenguin2打豆豆
leaf2打豆豆
motherPenguin打豆豆
childPenguin3打豆豆
leaf3打豆豆
childPenguin4打豆豆
leaf4打豆豆

说实话,我没有看到这个叶子节点加进去有啥意义,仅仅是标记一个结尾符么?或者说有其他具体的适用场景?这里我就不深究了。所以还是那句话,我们只需要理解每种设计模式的思想即可,实际的应用场景,完全不用照搬。

实际场景

查了网上的资料,大家都说组合模式在菜单、文件、文件夹的管理上用的非常多,因为他们是树形结构模式,不过实际的场景中,我只接触过购物车重构这块,所以我就简单说一下。

下面是小米商城购物车界面,可以看到里面有很多功能模块,你可以直接采用堆砌的方式实现每一个模块,然后依次调用每个模块具体的执行逻辑:

当然,我们也可以用组合模式,将购物车抽象成下面的树状结构(还有很多模块,仅列举一部分):

代码我就补贴了,核心实现就是通过组合模式将购物车的对象按照“总-分”关系组合在一起,最后执行购物车的Process()方法,就可以调用所有对象的Process()操作,从而完成每个模块对自身业务的逻辑处理。

结语

总结一下,后续如果你的代码需要处理成“总-分”关系,或者说是树形结构关系,最后通过一次调用完成所有对象的操作行为,那么就可以选择组合模式。

其实在写组合模式前,我没有找到项目中具体应用的场景,只看到网上的文章,说目录关系可以用组合模式,但是总感觉对这个模式的理解一直停留在表面,当我找到项目中购物车的实际运用场景时,我才感觉对这个模式有了更深入的理解。那大家也可以想想,自己做过的项目中,有哪些场景用到了组合模式呢?

欢迎大家多多点赞,更多文章,请关注微信公众号“楼仔进阶之路”,点关注,不迷路~~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存