10、设计模式-结构型模式-组合模式

组合模式

对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻
找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象)并调用执行,牵一而
动百,其中使用了递归调用的机制来对整个结构进行处理。

定义:

组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。

组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致
性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。

在组合模式中引入了抽象构件类Component,它是所有容器类和叶子类的公共父类,客户端针
对Component进行编程。

图解:

 角色:

Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,
在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的

子构件的方法,如增加子构件、删除子构件、获取子构件等。


Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在
抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。


Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子
节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象
构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子
节点的业务方法。

组合模式的关键是定义了一个抽象构件类

它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程

无须知道它到底表示的是叶子还是容器,可以对其进行统一处理

如果不使用组合模式,客户端代码将过多地依赖于容器对象复杂的内部实现结构,容器对象
内部实现结构的变化将引起客户代码的频繁变化,带来了代码维护复杂、可扩展性差等弊
端。组合模式的引入将在一定程度上解决这些问题。

抽象构件角色,其典型代码如下所示:

abstract class Component {
  public abstract void add(Component c); //增加成员
  public abstract void remove(Component c); //删除成员
  public abstract Component getChild(int i); //获取成员
  public abstract void operation(); //业务方法
}

一般将抽象构件类设计为接口或抽象类,将所有子类共有方法的声明和实现放在抽象构件类
中。对于客户端而言,将针对抽象构件编程,而无须关心其具体子类是容器构件还是叶子构件。

叶子构件,则其典型代码如下所示

class Leaf extends Component {
  public void add(Component c) {
    //异常处理或错误提示
  }
  public void remove(Component c) {
    //异常处理或错误提示
  }
  public Component getChild(int i) {
    //异常处理或错误提示
    return null;
  }
  public void operation() {
    //叶子构件具体业务方法的实现
  }
}

作为抽象构件类的子类,在叶子构件中需要实现在抽象构件类中声明的所有方法,包括业务
方法以及管理和访问子构件的方法,但是叶子构件不能再包含子构件,因此在叶子构件中实
现子构件管理和访问方法时需要提供异常处理或错误提示。

容器构件,则其典型代码如下所示

class Composite extends Component {
  private ArrayList<Component> list = new ArrayList<Component>();
  
public void add(Component c) {       list.add(c);   }
  
public void remove(Component c) {     list.remove(c);   }
  
public Component getChild(int i) {     return (Component)list.get(i);   }
  
public void operation() {     //容器构件具体业务方法的实现     //递归调用成员构件的业务方法     for(Object obj:list) {       ((Component)obj).operation();     }   } }

在容器构件中实现了在抽象构件中声明的所有方法,既包括业务方法,也包括用于访问和管
理成员子构件的方法,如add()、remove()和getChild()等方法。

需要注意的是在实现具体业务方法时,由于容器构件充当的是容器角色,包含成员构件

因此它将调用其成员构件的业务方法。

在组合模式结构中,由于容器构件中仍然可以包含容器构件,因此在对容器构件进行处
理时需要使用递归算法,即在容器构件的operation()方法中递归调用其成员构件的operation()方法。

事例:

文件夹存储文件

//抽象构建:抽象文件类
abstract class AbstractFile {
    public abstract void add(AbstractFile file);
    public abstract void remove(AbstractFile file);
    public abstract AbstractFile getChild(int i);
    public abstract void killVirus();
}
//叶子构件:图像文件类
public class ImageFile extends AbstractFile {

    String name;

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

    @Override
    public void add(AbstractFile file) {
        System.out.println("叶子节点没有此方法");
    }

    @Override
    public void remove(AbstractFile file) {
        System.out.println("叶子节点没有此方法");
    }

    @Override
    public AbstractFile getChild(int i) {
        System.out.println("叶子节点没有此方法");
        return null;
    }

    @Override
    public void killVirus() {
        System.out.println("----对图片文件'" + name + "'进行杀毒");
    }
}
//叶子构件:文本文件
public class TextFile extends AbstractFile {

    String name;

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

    @Override
    public void add(AbstractFile file) {
        System.out.println("叶子节点没有此方法");
    }

    @Override
    public void remove(AbstractFile file) {
        System.out.println("叶子节点没有此方法");
    }

    @Override
    public AbstractFile getChild(int i) {
        System.out.println("叶子节点没有此方法");
        return null;
    }

    @Override
    public void killVirus() {
        System.out.println("----对文件'" + name + "'进行杀毒");
    }
}
//容器构建:文件夹
public class Folder extends  AbstractFile {

    //定义集合fileList,用于存储AbstractFile类型的成员
    private  ArrayList<AbstractFile> fileList = new ArrayList<>();
    String name;

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

    @Override
    public void add(AbstractFile file) {
        fileList.add(file);
    }

    @Override
    public void remove(AbstractFile file) {
        fileList.remove(file);
    }

    @Override
    public AbstractFile getChild(int i) {
        return fileList.get(i);
    }

    @Override
    public void killVirus() {
        System.out.println("****对文件夹:" + name + "进行杀毒"); //模拟杀毒
        //递归调用成员的killVirus()方法
        for (Object obj : fileList){
            ((AbstractFile)obj).killVirus();
        }
    }
}
public class client {
    public static void main(String[] args) {

        //针对抽象构建编程
        AbstractFile file1,file2,file3,file4,folder1,folder2,folder3,folder4;

        folder1 = new Folder("MrChengs文件夹");
        folder2 = new Folder("图像文件夹");
        folder3 = new Folder("文件文件夹");
        folder4 = new Folder("视频文件夹");

        file1 = new ImageFile("file1.jpg");
        file2 = new ImageFile("file2.png");
        file3 = new TextFile("作业.doc");
        file4 = new TextFile("work.txt");


        //图像文件夹
        //--file1.jpg
        //--file2.png
        folder2.add(file1);
        folder2.add(file2);
        //文件文件夹
        //--作业.doc
        //--work.txt
        folder3.add(file3);
        folder3.add(file4);

        folder1.add(folder2);
        folder1.add(folder3);
        folder1.add(folder4);

        folder1.killVirus();
    }
}

图构:

无须修改源代码,符合“开闭原则”,客户端无须关心节点的层次结构

可以对所选节点进行统一处理,提高系统的灵活性。

 优点

(1) 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽
略了层次的差异,方便对整个层次结构进行控制。


(2) 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整
个组合结构,简化了客户端代码。


(3) 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符
合“开闭原则”。


(4) 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对
象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

 缺点

在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些
特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型
系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时
进行类型检查来实现,这个实现过程较为复杂

 适用场景

(1) 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以
一致地对待它们。


(2) 在一个使用面向对象语言开发的系统中需要处理一个树形结构。


(3) 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新
的类型。

猜你喜欢

转载自www.cnblogs.com/Mrchengs/p/10896484.html