1 概述

1.1 概述

对于树形构造,比方文件目录,一个文件夹中能够蕴含多个文件夹和文件,而一个文件中不能在蕴含子文件或者子文件夹,在这里能够称文件夹为容器,称文件为叶子

在树形构造中,当容器对象(比方文件夹)的某个办法被调用时,将遍历整个文件夹,寻找也蕴含这个办法的成员对象(容器对象或叶子对象)并调用执行。因为容器对象以及叶子对象在性能上的区别,应用这些对象的代码中必须有区别对待容器对象以及叶子对象,但大多数状况下须要一致性解决它们。

组合模式为解决此类问题而生,它能够让叶子对象以及容器对象的应用具备一致性。

1.2 定义

组合模式:组合多个对象造成树形构造以示意具备“整体-局部”关系的层次结构。组合模式对单个对象(叶子对象)和组合对象(容器对象)的应用具备一致性。
组合模式又叫“局部-整体”模式,它是一种对象结构型模式。

1.3 结构图

1.4 角色

  • Component(形象构件):能够是接口或者抽象类,为叶子构件和容器构件对象申明接口,在该角色中能够蕴含所有子类共有行为的申明和实现。在形象构件中定义了拜访以及治理它的子构件的办法,例如减少/删除/获取子构件
  • Leaf(叶子构件):示意叶子节点对象,叶子节点没有子节点,它实现了在形象构件中定义的行为,对于拜访以及治理子构件的办法,通常会抛出异样
  • Composite(容器构件):示意容器节点对象,容器节点蕴含子节点,其子节点能够是叶子节点,也能够是容器节点,它提供一个汇合用于存储子节点,实现了在形象构件中定义的行为,包含拜访以及治理子构件的办法

2 典型实现

2.1 步骤

组合模式的要害是定义了一个形象构件类,它既能够示意叶子也能够示意容器,客户端针对该形象构件进行编程,毋庸晓得到底是叶子还是容器,同时容器对象与形象构件之间须要建设一个聚合关联关系,在容器对象中既能够蕴含叶子也能够蕴含容器,以此实现递归组合,造成树形构造。

因而首先须要定义形象构件类,通用步骤如下:

  • 定义形象构件:定义形象构件类,增加四个根本办法:减少/删除/获取成员+业务办法,能够将形象构件类定义为抽象类或者接口
  • 定义叶子构件:继承或实现形象构件类,笼罩或实现具体业务办法,同时对于治理或拜访子构件的办法提供异样解决或谬误提醒
  • 定义容器构件:继承或实现形象构件类,笼罩或实现形象构件中的所有办法,一般来说容器构件会蕴含一个汇合公有成员用于保留形象构件,在业务办法中对这个汇合进行遍历从而实现递归调用

2.2 形象构件

形象构件个别定义如下:

abstract class Component{    abstract void add(Component c);    abstract void remove(Component c);    abstract Component getChild(int i);    abstract void operation();}

2.3 叶子构件

class Leaf extends Component{    public void add(Component c)    {        //叶子构件不能拜访该办法        System.out.println("谬误,不能拜访增加构件办法!");    }    public void remove(Component c)    {        //叶子构件不能拜访该办法        System.out.println("谬误,不能拜访删除构件办法!");    }    public Component getChild(int i)    {        //叶子构件不能拜访该办法        System.out.println("谬误,不能拜访获取构件办法!");        return null;    }    public void operation()    {        System.out.println("叶子业务办法");    }}

叶子构件只须要笼罩具体业务办法opeartion,对于治理子构件的办法能够提醒谬误或者抛出异样来解决。

2.4 容器构件

class Composite extends Component{    private ArrayList<Component> list = new ArrayList<>();        public void add(Component c)    {        list.add(c);    }    public void remove(Component c)    {        list.remove(c);    }    public Component getChild(int i)    {        return list.get(i);    }    public void operation()    {        list.forEach(Component::operation);    }}

容器构件只须要简略实现治理子构件的办法,对于业务办法个别须要对形象构件汇合进行遍从来实现递归调用。

3 实例

开发一个杀毒软件零碎,能够对某个文件夹或单个文件进行杀毒,还能依据文件类型的不同提供不同的杀毒形式,比方文本文件和图像文件的杀毒形式有所差别,应用组合模式对该零碎进行设计。

首先定义形象构件类AbstractFileFolder作为容器构件类,ImageFileTextFileVideoFile作为叶子构件类,代码如下:

public class Test{    public static void main(String[] args) {        AbstractFile file1,file2,file3,file4,folder1,folder2;        file1 = new ImageFile("图像文件1号");        file2 = new VideoFile("视频文件1号");        file3 = new TextFile("文本文件1号");        file4 = new ImageFile("图像文件2号");        folder1 = new Folder("文件夹1");        folder2 = new Folder("文件夹2");        try        {            folder2.add(file1);            folder2.add(file2);            folder2.add(file3);            folder1.add(file4);            folder1.add(folder2);        }        catch(IllegalAccessException e)        {            e.printStackTrace();        }                folder1.killVirus();        System.out.println();        folder2.killVirus();    }}//形象构件类abstract class AbstractFile{    protected String name;    abstract void add(AbstractFile file) throws IllegalAccessException;    abstract void remove(AbstractFile file) throws IllegalAccessException;    abstract AbstractFile getChild(int i) throws IllegalAccessException;    public void killVirus()    {        System.out.println(name+" 杀毒");    }}//叶子构件类class ImageFile extends AbstractFile{    public ImageFile(String name)    {        this.name = name;    }    public void add(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访增加文件办法!");    }    public void remove(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访删除文件办法!");    }    public AbstractFile getChild(int i)    {        throw new IllegalAccessException("谬误,不能拜访获取文件办法!");    }}//叶子构件类class TextFile extends AbstractFile{    public TextFile(String name)    {        this.name = name;    }    public void add(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访增加文件办法!");    }    public void remove(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访删除文件办法!");    }    public AbstractFile getChild(int i)    {        throw new IllegalAccessException("谬误,不能拜访获取文件办法!");    }}//叶子构件类class VideoFile extends AbstractFile{    public VideoFile(String name)    {        this.name = name;    }    public void add(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访增加文件办法!");    }    public void remove(AbstractFile c)    {        throw new IllegalAccessException("谬误,不能拜访删除文件办法!");    }    public AbstractFile getChild(int i)    {        throw new IllegalAccessException("谬误,不能拜访获取文件办法!");    }}//容器构件类class Folder extends AbstractFile{    private ArrayList<AbstractFile> list = new ArrayList<>();    public Folder(String name)    {        this.name = name;    }    public void add(AbstractFile c)    {        list.add(c);    }    public void remove(AbstractFile c)    {        list.remove(c);    }    public AbstractFile getChild(int i)    {        return list.get(i);    }    public void killVirus()    {        System.out.println("对 "+name+" 进行杀毒");        list.forEach(AbstractFile::killVirus);    }}

输出如下:

4 通明组合模式与平安组合模式

4.1 如何简化代码

只管组合模式的扩展性好,在下面的例子中减少新的文件类型毋庸批改原有代码,然而,因为形象构件类AbstractFile申明了与叶子构件无关的构件治理办法,因而 须要实现这些办法,这样就会带来很多重复性的工作。

解决方案有两个:

  • 形象构件提供默认实现:叶子构件中的构件治理办法转移到形象构件中提供默认实现
  • 形象构件删除办法:在形象构件中不提供治理构件的办法

4.2 默认实现

如果应用形象构件提供默认实现的办法,则上述例子代码简化如下:

abstract class AbstractFile{    protected String name;    public AbstractFile(String name)    {        this.name = name;    }    public void add(AbstractFile file) throws IllegalAccessException    {        throw new IllegalAccessException("谬误,不能拜访增加文件办法!");    }    public void remove(AbstractFile file) throws IllegalAccessException    {        throw new IllegalAccessException("谬误,不能拜访删除文件办法!");    }    public AbstractFile getChild(int i) throws IllegalAccessException    {        throw new IllegalAccessException("谬误,不能拜访获取文件办法!");    }    public void killVirus()    {        System.out.println(name+" 杀毒");    }}class ImageFile extends AbstractFile{    public ImageFile(String name)    {        super(name);    }}class TextFile extends AbstractFile{    public TextFile(String name)    {        super(name);    }}class VideoFile extends AbstractFile{    public VideoFile(String name)    {        super(name);    }}

在叶子构件中只有构造方法(实际上业务办法应该是形象的,在叶子构件中实现业务办法,这里的业务办法是killVirus(),这里是进行了简化),这样批改尽管简化了代码,然而总的来说为叶子构件提供这些办法是没有意义的,因为叶子不会再下一个档次的对象,这在编译阶段不会出错 ,然而在运行阶段可能会出错。

4.3 删除办法

如果应用形象构件删除办法的形式进行简化代码,则上述例子简化如下:

abstract class AbstractFile{    protected String name;    public AbstractFile(String name)    {        this.name = name;    }    abstract void killVirus();}class ImageFile extends AbstractFile{    public ImageFile(String name)    {        super(name);    }    public void killVirus()    {        System.out.println("图像文件"+name+"杀毒");    }}class TextFile extends AbstractFile{    public TextFile(String name)    {        super(name);    }    public void killVirus()    {        System.out.println("文本文件"+name+"杀毒");    }}class VideoFile extends AbstractFile{    public VideoFile(String name)    {        super(name);    }    public void killVirus()    {        System.out.println("视频文件"+name+"杀毒");    }}

这样做叶子构件就无法访问治理构件的办法了,然而带来的害处是客户端无奈对立针对形象构件类AbstractFile进行编程,批改之前代码如下:

AbstractFile file1,file2,file3,file4,folder1,folder2;

因为AbstractFile中删除了治理构件办法,因而客户端须要批改代码如下:

AbstractFile file1,file2,file3,file4;Folder folder1,folder2;

4.4 通明组合模式

通明组合模式就是第一种解决方案中的办法,在形象构件中申明所有用于治理构件的办法,这样做的益处是确保所有的构件类都具备雷同的接口,客户端能够针对形象构件进行对立编程,结构图如下:

通明组合模式的毛病是不够平安,因为叶子对象和容器对象在实质上是有区别的。叶子对象不可能有下一档次的对象,提供治理构件的办法是没有意义的,在编译阶段不会报错,然而在运行阶段可能会出错。

4.5 平安组合模式

平安组合模式就是第二种办法的方法,平安组合模式中,形象构件没有申明治理构件的办法,而是在容器构件中增加治理构件的办法,这种做法是平安的因为叶子对象不可能调用到这些办法。结构图如下:

平安组合模式的毛病是不够通明,因为叶子构件与容器构件具备不同的办法,治理构件的办法在容器构件中定义,客户端不能齐全针对形象构件进行编程,必须有区别地看待叶子构件与容器构件。

5 次要长处

  • 档次管制:组合模式能够分明定义分档次的简单对象,示意对象的全副或者局部档次,它让客户端疏忽了档次的差别,不便对整个层次结构进行管制
  • 统一应用构件:客户端能够统一地应用容器构件或者叶子构件,也就是能针对构件形象层一致性编程
  • 扩展性好:减少新的容器构件或者叶子构件都很不便,合乎开闭准则
  • 无效针对树形构造:组合模式为树形构造的面向对象实现提供了一种灵便的解决方案,通过叶子构件与容器构件的递归组合,能够造成简单的树形构造,但管制树形构造却很简略

6 次要毛病

  • 难以限度构件类型:减少新构件时难以限度构件类型,比方心愿容器构件中只有某一特定类型的叶子构件,例如一个只能蕴含图片的文件夹,应用组合模式时不能依赖类型零碎来施加这些束缚,须要再运行时进行类型查看来实现,过程较为简单

7 实用场景

  • 具备整体和局部的层次结构中,心愿通过一种形式疏忽整体与局部的差别,客户端能够一致性看待它们
  • 解决树形构造
  • 零碎中可能拆散出叶子构件以及容器构件,而且类型不固定,须要减少新的叶子构件或者容器构件

8 总结