美文网首页设计模式专题
设计模式-组合模式

设计模式-组合模式

作者: RUMyCola | 来源:发表于2022-09-06 07:02 被阅读0次

    设计模式-组合模式

    定义

    我们知道古代的皇帝想要管理国家,是不可能直接管理到具体每一个老百姓的,因此设置了很多机构,比如说三省六部,这些机构下面又有很多小的组织.他们共同管理着这个国家.再比如说,一个大公司,下面有很多小的部门,每一个部门下面又有很多个部门.说到底这就是组合模式.

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

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

    树形结构

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

    组合模式包含3个角色:

    1、抽象根节点(Component):定义系统各层次对象共有方法和属性,可以预先定义一些默认行为和属性.

    2、树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构.

    3、叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位.

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

    组合模式的应用场景

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

    组合模式主要应用场景:

    1、希望客户端可以忽略组合对象与单个对象的差异时;

    2、对象层次具备整体和部分,呈树形结构.

    在我们生活中的组合模式也非常常见,比如树形菜单、操作系统目录结构、公司组织架构等.

    透明组合模式写法

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

    透明组合模式UML图示例

    来看这个例子,还是以学习课程为例.这次我们来设计一个课程的关系结构.比如我们有Java入门课程、人工智能课程、Java设计模式、源码分析、软技能等课程,而Java设计模式、源码分析、软技能又属于Java架构师系列课程包,且每个课程的定价都不一样.但是,这些课程不论怎么组合,都有一些共性,而且是整体和部分的关系,可以用组合模式来设计.先创建一个顶层的抽象组件CourseComponent类:

    抽象根节点

    把所有可能用到的方法都定义到这个最顶层的抽象类中,但是不写任何逻辑处理代码,而是直接抛异常.

    注意:这里不使用抽象方法.因为用了抽象方法,其子类都必须实现,这样就不能体现各个子类间的差异.

    子类继承次抽象类后,只需要重写有差异的方法覆盖父类的方法即可.

    叶子节点 树枝节点 测试代码 运行结果

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

    下面 我们再看一下安全组合模式.

    安全组合模式写法

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

    安全组合模式UML示例(相对上例)

    再举一个程序员更熟悉的例子.对于程序员来说,电脑是每天都要接触的.电脑的文件系统是一个典型的树状结构,目录包含文件夹和文件,文件夹里面有可以包含文件夹和文件,我们用代码来实现一个目录系统.

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

    抽象根节点 叶子节点 树枝节点

    注意:Folder类不仅覆盖了顶层的show()方法,而且还增加了list()方法.

    测试代码 运行结果

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

    组合模式在源码中的应用

    组合模式在源码中应用也是非常广泛的.首先,我们来看一个非常熟悉的HashMap,它里面有一个putAll()方法:

    HashMap中的putAll(Map<k, v> m)方法 继续下钻可以看到Node操作 Node(叶子节点)

    我们看到putAll()方法传入的是Map对象,Map就是一个抽象构件(同时这个构件中只支持键值对的存储格式),而HashMap是一个中间构件,HashMap中的Node节点就是叶子节点.说到中间构件就会有规定的存储方式.HashMap中的存储方式是一个静态内部类的数组Node<K,V>[] tab,其源码如下:

    基本哈希 bin 节点,用于大多数条目(叶子节点)

    同理,我们常用的ArrayList对象也有addAll()方法,其参数也是ArrayList的父类Collection,来看源码:

    ArrayList对象的addAll()方法

    组合对象和被组合对象都应该有统一的接口实现或者统一的抽象父类.在这里我再举一个开源框架中非常经典的案例,MyBatis解析各种Mapping文件中的SQL,设计了一个非常关键的类叫做SqlNode,xml中的每一个Node都会解析一个SqlNode对象,最后把所有的SqlNode拼接在一起就成了一条完整的SQL语句,他的顶端设计非常简单.show me the code:

    抽象接口SqlNode

    apply()方法会根据传入的参数context,参数解析该SqlNode所记录的SQL片段,DynamicContext.appendSql()方法将解析后的SQL片段追加到DynamicContext的ContextMap(其实是一个HashMap)容器中保存.当SQL节点下的所有SqlNode完成解析后,可以通过DynamicContext.getSql()获取一条完整的SQL语句.下面是它的UML图:

    SqlNode组合模式UML类图

    组合模式的优缺点

    很多小伙伴肯定还有个疑问,既然组合模式会被分为两种实现,那么肯定是不同的场合某一种会更加适合,也要具体情况具体分析.透明组合模式将公共接口封装到抽象根节点(Component)中,那么系统所有节点就具备一致行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用透明组合模式也许更好(代价:为剩下少数层次节点引入不需要的方法);而如果当系统各个层次差异性行为较多或树节点层次相对稳定(健壮)时,采用安全组合模式.

    优点

    1、清楚地定义分层次的复杂对象,表示对象的全部或部分层次;

    2、让客户端忽略了层次的差异,方便对整个层次结构进行控制;

    3、简化客户端代码;

    4、符合开闭原则.

    缺点

    1、限制类型时会较为复杂;

    2、使设计变得更加抽象.

    相关文章

      网友评论

        本文标题:设计模式-组合模式

        本文链接:https://www.haomeiwen.com/subject/raoenrtx.html