设计模式之【组合模式】,树形结构的完美解决方案

2023-05-16

文章目录

  • 一、什么是组合模式
    • 1、组合模式三大角色
    • 2、组合模式应用场景
    • 3、组合模式注意事项和细节
  • 二、透明组合模式
    • 1、学院院系案例
    • 2、透明组合模式总结
  • 三、安全组合模式
    • 1、linux目录系统案例
    • 2、安全组合模式总结
  • 四、源码中使用的组合模式
    • 1、HashMap
    • 2、ArrayList
    • 3、MyBatis

一、什么是组合模式

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

组合模式依据树形结构来组合对象,用来表示部分以及整体层次。`

GOF24原文:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式

1、组合模式三大角色

组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,最顶层的节点称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点。如下图所示:
在这里插入图片描述
由上图可以看出,其实根节点和树枝节点本质上是同一种数据类型,可以作为容器使用:而叶子节点与树枝节点在语义上不属于同一种类型,但是在组合模式中,会把树枝节点和叶子节点认为是同一种数据类型(用同一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象都是同一种类型,带来的一个好处就是客户无需辨别树枝节点还是叶子节点,而是可以直接进行操作,给客户使用带来极大的便利。

组合模式包含3个角色:

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

组合模式在代码实现上,有两种不同的方式,分别是透明组合模式和安全组合模式

2、组合模式应用场景

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

(1)希望客户端可以忽略组合对象与单个对象的差异时;
(2)对象层次具备整体和部分,呈树形结构。

常用的组合模式场景:
树形菜单;操作系统目录结构;公司组织架构等等。
在这里插入图片描述

3、组合模式注意事项和细节

(1)简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
(2)具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动。
(3)方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
(4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式。
(5)要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

二、透明组合模式

透明组合模式是把所有公共方法都定义在Component中,这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口。其UML类图如下所示:
在这里插入图片描述

1、学院院系案例

我们以一个学校的院系结构为例,一个学校有多个学院,一个学院有多个系。

先定义抽象根节点:

// 抽象根节点
public abstract class OrganizationComponent {

	private String name; // 名字
	private String des; // 说明
	
	protected  void add(OrganizationComponent organizationComponent) {
		//默认实现
		throw new UnsupportedOperationException();
	}
	
	protected  void remove(OrganizationComponent organizationComponent) {
		//默认实现
		throw new UnsupportedOperationException();
	}

	//构造器
	public OrganizationComponent(String name, String des) {
		super();
		this.name = name;
		this.des = des;
	}
	// 省略 get  set
	
	//方法print, 做成抽象的, 子类都需要实现
	protected abstract void print();
	
}

大学:

//University 就是 Composite , 可以管理College
public class University extends OrganizationComponent {
	// 定义学院集合 College
	List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

	// 构造器
	public University(String name, String des) {
		super(name, des);
	}

	// 重写add
	@Override
	protected void add(OrganizationComponent organizationComponent) {
		organizationComponents.add(organizationComponent);
	}

	// 重写remove
	@Override
	protected void remove(OrganizationComponent organizationComponent) {
		organizationComponents.remove(organizationComponent);
	}

	@Override
	public String getName() {
		return super.getName();
	}

	@Override
	public String getDes() {
		return super.getDes();
	}

	// print方法,就是输出University 包含的学院
	@Override
	protected void print() {
		System.out.println("--------------" + getName() + "--------------");
		//遍历 organizationComponents 
		for (OrganizationComponent organizationComponent : organizationComponents) {
			organizationComponent.print();
		}
	}
}

学院:

public class College extends OrganizationComponent {

	//List 中 存放的Department
	List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

	// 构造器
	public College(String name, String des) {
		super(name, des);
	}

	// 重写add
	@Override
	protected void add(OrganizationComponent organizationComponent) {
		//  将来实际业务中,Colleage 的 add 和  University add 不一定完全一样
		organizationComponents.add(organizationComponent);
	}

	// 重写remove
	@Override
	protected void remove(OrganizationComponent organizationComponent) {
		organizationComponents.remove(organizationComponent);
	}

	@Override
	public String getName() {
		return super.getName();
	}

	@Override
	public String getDes() {
		return super.getDes();
	}

	// print方法,就是输出University 包含的学院
	@Override
	protected void print() {
		System.out.println("--------------" + getName() + "--------------");
		//遍历 organizationComponents 
		for (OrganizationComponent organizationComponent : organizationComponents) {
			organizationComponent.print();
		}
	}
}

专业(叶子节点):

public class Department extends OrganizationComponent {

	//没有集合
	
	public Department(String name, String des) {
		super(name, des);
	}

	//add , remove 就不用写了,因为他是叶子节点
	@Override
	public String getName() {
		return super.getName();
	}
	
	@Override
	public String getDes() {
		return super.getDes();
	}
	
	@Override
	protected void print() {
		System.out.println(getName());
	}
}

测试类:

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//从大到小创建对象 学校
		OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");
		
		//创建 学院
		OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
		OrganizationComponent infoEngineercollege = new College("信息工程学院", " 信息工程学院 ");
		
		
		//创建各个学院下面的系(专业)
		computerCollege.add(new Department("软件工程", " 软件工程不错 "));
		computerCollege.add(new Department("网络工程", " 网络工程不错 "));
		computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
		
		//
		infoEngineercollege.add(new Department("通信工程", " 通信工程不好学 "));
		infoEngineercollege.add(new Department("信息工程", " 信息工程好学 "));
		
		//将学院加入到 学校
		university.add(computerCollege);
		university.add(infoEngineercollege);
		
		//university.print();
		university.print();
	}

}

最终的UML类图如下:
在这里插入图片描述

2、透明组合模式总结

透明组合模式把所有公共方法都定义在Component中,这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口;缺点是叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法,这与设计模式 接口隔离原则相违背。

三、安全组合模式

安全组合模式是只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加、删除等)放到自身当中。其UML类图如下所示:
在这里插入图片描述

1、linux目录系统案例

文件系统有两大层次:文件夹和文件。其中,文件夹能容纳文件,为树枝节点;文件为叶子节点。由于目录系统层次较少,且树枝节点(文件夹)结构相对稳定,而文件其实可以有很多类型,所以这里我们选择使用安全组合模式来实现目录系统,可以避免为叶子类型(文件)引入冗余方法。

// 顶层抽象
public abstract class Directory {

    protected String name;

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

    public abstract void show();
}
// 目录
public class Folder extends Directory {
    private List<Directory> dirs;

    private Integer level;

    public Folder(String name,Integer level) {
        super(name);
        this.level = level;
        this.dirs = new ArrayList<Directory>();
    }

    @Override
    public void show() {
        System.out.println(this.name);
        for (Directory dir : this.dirs) {
            //控制显示格式
            if(this.level != null){
                for(int  i = 0; i < this.level; i ++){
                    //打印空格控制格式
                    System.out.print("  ");
                }
                for(int  i = 0; i < this.level; i ++){
                    //每一行开始打印一个+号
                    if(i == 0){ System.out.print("+"); }
                    System.out.print("-");
                }
            }
            //打印名称
            dir.show();
        }
    }

    public boolean add(Directory dir) {
        return this.dirs.add(dir);
    }

    public boolean remove(Directory dir) {
        return this.dirs.remove(dir);
    }

    public Directory get(int index) {
        return this.dirs.get(index);
    }

    public void list(){
        for (Directory dir : this.dirs) {
            System.out.println(dir.name);
        }
    }

}
// 文件
public class File extends Directory {

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

    @Override
    public void show() {
        System.out.println(this.name);
    }

}
// 测试类
class Test {
    public static void main(String[] args) {

        System.out.println("============安全组合模式===========");

        File qq = new File("QQ.exe");
        File wx = new File("微信.exe");

        Folder office = new Folder("办公软件",2);

        File word = new File("Word.exe");
        File ppt = new File("PowerPoint.exe");
        File excel = new File("Excel.exe");

        office.add(word);
        office.add(ppt);
        office.add(excel);

        Folder wps = new Folder("金山软件",3);
        wps.add(new File("WPS.exe"));
        office.add(wps);

        Folder root = new Folder("根目录",1);
        root.add(qq);
        root.add(wx);
        root.add(office);

        System.out.println("----------show()方法效果-----------");
        root.show();

        System.out.println("----------list()方法效果-----------");
        root.list();
    }
}

我们看一下UML类图:
在这里插入图片描述

2、安全组合模式总结

安全组合模式的好处是接口定义职责清晰,符合设计模式 单一职责原则和 接口隔离原则;缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式 依赖倒置原则。

四、源码中使用的组合模式

1、HashMap

Map就是一个抽象构建(同时这个构建中只支持键值对的存储格式),而HashMap是一个中间构建,HashMap中的Node节点就是叶子节点。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    // ...
	transient Node<K,V>[] table;
	// ...
}	

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

其putAll方法的入参就是Map:

public void putAll(Map<? extends K, ? extends V> m) {
    putMapEntries(m, true);
}

2、ArrayList

ArrayList的addAll方法,其参数也是父类Collection:

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

3、MyBatis

MyBatis中解析各种Mapping文件中的SQL,设计了一个非常关键的类叫做SqlNode,xml中的每一个Node都会解析为一个SqlNode对象,最后把所有的SqlNode拼装到一起就成了一条完整的SQL语句,它的顶层设计非常简单。来看源代码:

public interface SqlNode {
  boolean apply(DynamicContext context);
}

Apply方法会根据传入的参数context,参数解析该SqlNode所记录的SQL片段,并调用DynamicContext.appendSql()方法将解析后的SQL片段追加到DynamicContext的SqlBuilder中保存。当SQL节点下所有的SqlNode完成解析后,可以通过DynamicContext.getSql()获取一条完整的SQL语句。

我们看一下类图:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式之【组合模式】,树形结构的完美解决方案 的相关文章

  • redis cli笔记

    此篇为人个笔记 xff0c 基本是个人常用命令 xff0c 仅供参考 基础操作 redis cli redis span class token number 127 0 span 0 1 637 span class token oper
  • Jsoup通过curl Request设置header

    使用Charles时发现它有一个功能非常的方便 Copy cURL Request菜单 可以把请求中的header信息自动拼接成curl的参数 其实有很多的软件都具备这样的功能 那么如何把这些header信息直接 放到Jsoup的heade
  • 【Java题】用户数据中的身高体重收集

    题目要求 用户数据中的身高体重收集 应用程序中用户数据收集 xff0c 非常重要 xff0c 是大数据重要来源之一 在某APP场景中收集用户的身高体重数据 xff0c 身高单位 米 xff0c 体重单位kg xff0c 要求用户输入的任何数
  • archlinux i3wm通知管理

    通知管理 Dunst 是大多数桌面环境提供的通知守护程序的轻量级替代品 它非常可定制 xff0c 不依赖于任何工具包 xff0c 因此适合那些我们都喜欢定制到完美的以窗口管理器为中心的设置 官网如下 xff1a https dunst pr
  • i3wm 获取window class

    在i3wm中如果你想让一个程序固定在某个workspace中打开可以如下设置 span class token comment 打开virtual box直接进入第10个桌面 span assign span class token pun
  • xrandr修改分辨率与刷新率

    查询当前显示器信息 直接运行xrandr即可 我的执行结果如下 xff0c 信息过多 xff0c 我则把关键部分放出来 xff1a itkey 64 vivobook screenlayout xrandr span class token
  • 设计模式之【适配器模式】,两个人之间确实需要月老的搭线~

    文章目录 一 什么是适配器模式1 适配器模式使用场景2 代理 桥接 装饰器 适配器 4 种设计模式的区别3 适配器模式结构 二 类适配器1 实例 三 对象适配器1 实例 四 接口适配器1 实例 五 源码中的应用 一 什么是适配器模式 适配器
  • linux黑客帝国cmatrix

    装X神器 xff0c 黑客帝国 xff01 xff01 xff01 安装方法 我这里以archlinux为例 span class token function sudo span pacman S cmatrix 效果演示
  • mysql字段userid逗号分开保存按userid查询

    我的sql水平一般 xff0c 仅是一个笔记 无法保存是最优解 仅供参考 场景 有一张消息表 xff0c 其中有一个收信人字段中把多个用户以 分隔保存信息 我需要根据userid来查询信息 为了方便理解 xff0c 我减化一下表结构 我的表
  • windows11 + linux 蓝牙连接问题

    我主要使用Linux办公 xff0c 因为一些特殊情况需要到windows系统下测试 这时就会发现蓝牙键盘就需要重新连接 xff0c 只要切一次系统就要重新连接一次非常的麻烦 今天要多次往返这两个系统 xff0c 所以我决定解决一下这个问题
  • archlinux音量管理

    我用的i3wm 平时音量调整 xff0c 我是直接使用键盘自带的多媒体键实现的 xff0c 所以一直也懒得折腾 多媒体键盘调整音量的缺点就是无法细力度调整 xff0c 导致使用耳机听歌时 xff0c 要么声音听不到 xff0c 要么就是听不
  • git 命令行版本初始化

    假设你已经在网页上创建了一个如下的版本库 http git ycmit cn r manuli api git 然后我们需要把本地已经有的文件上传到版本库 xff0c 执行以下命令即可 span class token function g
  • mysql转SQL Server

    使用工具 Navicat 点击 工具 61 数据传输 即可完成
  • jdbc SQLServer Error: “The server selected protocol version TLS10

    最近在尝试在SQL Server 环境下开发新项目 xff0c 遇到了一些坑 xff0c 记录一下 报错信息 com span class token punctuation span microsoft span class token
  • windows11右键菜单变回windows10风格

    windows11的右键菜单会折叠一部分 xff0c 虽然美观了不少 但是总感觉效率反而更低了 xff0c 能不能设置回windows10风格的呢 xff1f 操作 Windows 43 R xff0c 输入 regedit 并按 Ente
  • C# winform使用SQLite

    本文仅是一个笔记 xff0c 仅供参考 SQLite SQLite是遵守ACID的关系数据库管理系统 xff0c 它包含在一个相对小的C程序库中 与许多其它数据库管理系统不同 xff0c SQLite不是一个客户端 服务器结构的数据库引擎
  • C# SQLite Database Locked exception

    现象 在查询时没有问题 xff0c 但是在Insert时会报数据库被锁定 原因分析 可能是代码某个地方连接着数据库 xff0c 忘记关闭了 解决办法 原理我还没有完全搞懂 xff0c 不过根据下面的写法确实解决了问题 在某个地方 xff0c
  • 设计模式之【桥接模式】,多用组合少用继承

    文章目录 一 什么是桥接模式1 使用场景2 代理 桥接 装饰器 适配器 4 种设计模式的区别3 桥接模式的优缺点4 桥接模式的四种角色 二 实例桥接模式优化代码 三 源码中使用的桥接模式1 桥接模式在JDBC中的应用 一 什么是桥接模式 桥
  • C# 无操作则退出登陆

    span class token keyword using span span class token namespace System span span class token punctuation span span class
  • C#调用explorer.exe打开指定目录

    需求 C 程序上有一个按钮 xff0c 点击打开电脑上对应的目录即可 代码 span class token comment 打开帮助文档 span System span class token punctuation span Diag

随机推荐

  • windows中ncdu替代者TreeSize

    平时开发大部分时间使用linux或者macOS系统 xff0c 近期因为要开发C 程序不得不使用windows系统 在linux或者macOS常用的ncdu来查看文件目录体积大小 到windows中无法使用 xff0c 很不习惯 就想找一个
  • Visual Studio中vim模拟器

    简介 Vim 仿真 这是 Visual Studio 2015 及更高版本的 Vim 仿真层 它将 Vim 熟悉的键绑定体验直接集成到 Visual Studio 的编辑器中 细节 GitHub 上提供了该项目和问题跟踪的完整源代码 htt
  • mybatis小示例

    一般使用mybatis的环境 xff0c 大多都是别人已经配置好的 直接用就好了 xff0c 如何自己搭建呢 xff1f 其实很简单 看官方的文档就可以解决了 主要为了学习mybatis最基础的配置 我文章中的方法不基于spring 一般很
  • spring boot集成mybatis报错 java.lang.IllegalStateException: No supported DataSource type found

    背景 我参考MyBatis Spring的文档搭建环境 https mybatis org spring zh getting started html 完全按文档上操作 xff0c 还是报以下错误 报错信息 Error starting
  • Spring boot mybatis 简单示例

    我在Spring boot中集成mybatis竟然花了不少时间 xff0c 真没想到 对着官网的文档做 xff0c 竟然还花了这么多时间 所以我把过程尽可能的详细记录下来 xff0c 给有需要的朋友 需求 在spring boot 中使用m
  • spring boot + mybatis+ mysql环境搭建

    最近在尝试从0开始搭建框架 xff0c 结果在mybatis这块就踩了很多坑 于是就决定写篇文章记录一下 要求 尽可能的简单 xff0c 减少依赖 实战 新建spring boot项目 基于spring boot 的 xff0c 所以第一步
  • javascript字符串转对象

    永远不要使用 eval xff01 eval 是一个危险的函数 xff0c 它使用与调用者相同的权限执行代码 如果你用 eval 运行的字符串代码被恶意方 xff08 不怀好意的人 xff09 修改 xff0c 您最终可能会在您的网页 扩展
  • fastjson 属性排序

    fastjson把bean转成json字符串 xff0c 默认的顺序并不是按你实体类属性写的顺序来的 虽然属性顺序一般不会影响功能 xff0c 但是强迫症忍不了 因为java中通过反射机制是无法取到属性的顺序的 xff08 我猜测 xff0
  • 设计模式之【装饰者模式】,实现“穿衣打扮”自由原来这么简单

    文章目录 一 什么是装饰者模式1 装饰者模式原理2 装饰者模式四大角色3 代理 桥接 装饰器 适配器 4 种设计模式的区别4 装饰者模式的应用场景5 装饰者模式和代理模式的对比6 装饰者模式优缺点7 抽象装饰器 xff08 Decorato
  • mysql root允许远程连接

    在开发测试环境 xff0c 有时为了方便我们需要让root用户也可以远程连接 下面就讲一下如何设置 简明教程 连接mysql 不废话3行命令搞定 xff0c 不明白的看下面分步讲解 mysql span class token operat
  • windows http-server 因为在此系统上禁止运行脚本

    我想在windows中运行 http server 安装方法 xff1a span class token function npm span span class token function install span http serv
  • windows开启远程桌面

    现在的远程桌面工具很丰富 xff0c 向日葵 xff0c ToDesk TeamViewer 都是很简单易用的 但是在没有外网的场景下这些软件就不太好用了 今天来简单讲一下如果开启windows自带的远程桌面功能 环境 我这里以window
  • windows局域网传文件5种常用方法

    谈到文件分享 xff0c 在可以连接外网的情况下 xff0c 方法数不胜数 比如 xff1a QQ xff0c 微信 xff0c 邮件等等 那么如果无有外网 xff0c 在局域网下如何传输 远程桌面 在不安装第三方软件的情况下 xff0c
  • windows 11文件夹共享踩坑记录

    在windows 2008R2做文件共享一次就成功了 xff0c 没有什么特别之处 但是我在windows11中做文件共享 xff0c 请求的时候 总是提示 xff1a 登录失败 未知的用户名或错误密码 解决思路 在网上找答案 xff0c
  • SQL Server 2014安装笔记

    最近要部署一个项目 xff0c 需要用到SQL Server 2014 我把安装过程简单记录一下 xff0c 给有需要的朋友吧 下载安装包 在国内微软的官网下载速度还是比较慢的 xff0c 我是从 https msdn itellyou c
  • archlinux安装nodejs版本管理器nvm

    AUR安装 yay S nvm 安装完成以后并不是立马可以使用 xff0c 具体看下面 官方说明如下 xff1a You need to span class token builtin class name source span nvm
  • windows2008 JDK8安装内部错误61003

    在windows 2008 R2服务器上安装JDK8 Update341时 xff0c 出错报错内部错误61003情况 现象 原因分析 没有装visual c 43 43 2015 redistributable 解决办法 安装 visua
  • SQL Server Management Studio 访问远程数据库

    刚拿到这个软件时 xff0c 只会连接本地的数据库 服务器名称下拉框中的数据不知道怎么维护 后来才发现这个远程可能当作输入框使用 输入连接字符串就可以连接了 连接字符串 比如java中的jdbc连接字符串是 xff1a jdbc sqlse
  • SqlServer 导出导入表结构与数据

    在运维和数据备份的时候 xff0c 偶尔会需要导出表结构与数据 那么具体如何操作呢 xff1f 事先说明一下 xff0c 本文写的比较仓促 xff0c 质量一般 xff0c 仅供参考 环境 这里以Microsoft SQL Server M
  • 设计模式之【组合模式】,树形结构的完美解决方案

    文章目录 一 什么是组合模式1 组合模式三大角色2 组合模式应用场景3 组合模式注意事项和细节 二 透明组合模式1 学院院系案例2 透明组合模式总结 三 安全组合模式1 linux目录系统案例2 安全组合模式总结 四 源码中使用的组合模式1