设计模式之组合模式

简介

Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示 “部分-整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

组合模式(Composite Pattern) 也称为 整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。

使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

组合模式 一般用来描述 整体部分 的关系,它将对象组织到树形结构中,最顶层的节点称为 根节点,根节点下面可以包含 树枝节点叶子节点,树枝节点下面又可以包含 树枝节点叶子节点。如下图所示:

树形结构

由上图可以看出,其实 根节点树枝节点 本质上是同一种数据类型(蓝色圆圈),可以作为容器使用;而 叶子节点树枝节点 在语义上不属于同一种类型,但是在 组合模式 中,会把 树枝节点叶子节点 认为是同一种数据类型(用同一接口定义),让它们具备一致行为。这样,在 组合模式 中,整个树形结构中的对象都是同一种类型,带来的一个好处就是客户无需辨别 树枝节点 还是 叶子节点,而是可以直接进行操作,给客户使用带来极大的便利。

组合模式 核心:借助同一接口,使叶子节点和树枝节点的操作具备一致性。

主要解决

当子系统与其内各个对象层次呈现树形结构时,可以使用 组合模式 让该子系统内各个对象层次的行为操作具备一致性。客户端使用该子系统内任意一个层次对象时,无须进行区分,直接使用通用操作即可,为客户端的使用带来了便捷。

:如果树形结构系统不使用 组合模式 进行架构,那么按照正常的思维逻辑,对该系统进行职责分析,按上文树形结构图所示,该系统具备两种对象层次类型:树枝节点和叶子节点。那么我们就需要构造两种对应的类型,然后由于树枝节点具备容器功能,因此树枝节点类内部需维护多个集合存储其他对象层次(eg:List<Composite>,List<Leaf>),如果当前系统对象层次更复杂时,那么树枝节点内就又要增加对应的层次集合,这对树枝节点的构建带来了巨大的复杂性,臃肿性以及不可扩展性。同时客户端访问该系统层次时,还需进行层次区分,这样才能使用对应的行为,给客户端的使用也带来了巨大的复杂性。而如果使用 组合模式 构建该系统,由于 组合模式 抽取了系统各个层次的共性行为,具体层次只需按需实现所需行为即可,这样子系统各个层次就都属于同一种类型,所以树枝节点只需维护一个集合(List<Component>)即可存储系统所有层次内容,并且客户端也无需区分该系统各个层次对象,对内系统架构简洁优雅,对外接口精简易用。

优缺点

优点

  • 组合模式 屏蔽了对象系统的层次差异性(树节点和叶子节点为不同类型),将客户代码与复杂的容器对象解耦,使得客户端可以忽略层次间的差异,使用一致的行为控制不同层次。
  • 组合模式 可以很方便地增加 树枝节点叶子节点 对象,并对现有类库无侵入,符合 开闭原则

缺点

  • 如果类系统(树形结构)过于庞大,虽然对不同层次都提供一致性操作,但客户端仍需花费时间理清类之间的层次关系;
  • 组合模式 在具体实现上违背了设计模式 接口隔离原则依赖倒置原则

使用场景

  • 系统对象层次具备整体和部分,呈树形结构,且要求具备统一行为(如树形菜单,操作系统目录结构,公司组织架构等);

模式讲解

组合模式 主要包含三种角色:

  • 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性;
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构;
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位;

组合模式 在代码具体实现上,有两种不同的方式:

package com.yn.design_pattern.composite.universal.transparent;

import java.util.ArrayList;
import java.util.List;

class Client {
    public static void main(String[] args) {
        // 来一个根节点
        Component root = new Composite("root");
        // 来一个树枝节点
        Component branchA = new Composite("---branchA");
        Component branchB = new Composite("------branchB");
        // 来一个叶子节点
        Component leafA = new Leaf("------leafA");
        Component leafB = new Leaf("---------leafB");
        Component leafC = new Leaf("---leafC");

        root.addChild(branchA);
        root.addChild(leafC);
        branchA.addChild(leafA);
        branchA.addChild(branchB);
        branchB.addChild(leafB);

        String result = root.operation();
        System.out.println(result);

    }

    // 抽象根节点
    static abstract class Component {
        protected String name;

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

        public abstract String operation();

        public boolean addChild(Component component) {
            throw new UnsupportedOperationException("addChild not supported!");
        }

        public boolean removeChild(Component component) {
            throw new UnsupportedOperationException("removeChild not supported!");
        }

        public Component getChild(int index) {
            throw new UnsupportedOperationException("getChild not supported!");
        }
    }

    // 树节点
    static class Composite extends Component {
        private List<Component> mComponents;

        public Composite(String name) {
            super(name);
            this.mComponents = new ArrayList<>();
        }

        @Override
        public String operation() {
            StringBuilder builder = new StringBuilder(this.name);
            for (Component component : this.mComponents) {
                builder.append("\n");
                builder.append(component.operation());
            }
            return builder.toString();
        }

        @Override
        public boolean addChild(Component component) {
            return this.mComponents.add(component);
        }

        @Override
        public boolean removeChild(Component component) {
            return this.mComponents.remove(component);
        }

        @Override
        public Component getChild(int index) {
            return this.mComponents.get(index);
        }
    }

    //叶子节点
    static class Leaf extends Component {

        public Leaf(String name) {
            super(name);
        }

        @Override
        public String operation() {
            return this.name;
        }
    }
}

透明组合模式 中,由于 Component 包含叶子节点所不需要的方法,因此,我们直接将这些方法默认抛出UnsupportedOperationException异常。

2.安全模式:统一行为(Component)只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中;其 UML 类图如下所示:

安全组合模式 把系统各层次公有的行为定义在 Component 中,把组合(树节点)特有的行为(管理子类增加,删除等)放到自身(Composite)中。这样做的好处是接口定义职责清晰,符合设计模式 单一职责原则接口隔离原则;缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式 依赖倒置原则

    // 抽象根节点
    static abstract class Component {
        protected String name;

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

        public abstract String operation();
    }
  • 修改客户端代码:将树枝节点类型更改为 Composite 类型,以便获取管理子类操作的方法:
  • class Client {
        public static void main(String[] args) {
            // 来一个根节点
            Composite root = new Composite("root");
            // 来一个树枝节点
            Composite branchA = new Composite("---branchA");
            Composite branchB = new Composite("------branchB");
            // 来一个叶子节点
            Component leafA = new Leaf("------leafA");
            Component leafB = new Leaf("---------leafB");
            Component leafC = new Leaf("---leafC");
    
            root.addChild(branchA);
            root.addChild(leafC);
            branchA.addChild(leafA);
            branchA.addChild(branchB);
            branchB.addChild(leafB);
    
            String result = root.operation();
            System.out.println(result);
        }
    }

    上述例子的运行结果如下:

  • root
    ---branchA
    ------leafA
    ------branchB
    ---------leafB
    ---leafC
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值