设计模式--组合模式

一、树形结构
树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等。


设计模式--组合模式_第1张图片
image.png

可以看出,在上图中包含两种不同的元素:文件(蓝色节点)和文件夹(白色节点),其中在文件夹中即可以包含文件,也可以包含子文件夹,但是在文件中就不能再包含子文件或者子文件夹了。在此,我们可以称文件夹为容器(Container),而不同类型的各种文件是其成员,也称为叶子(Leaf),一个文件夹也可以作为另一个更大的文件夹的成员。

二、树形结构的普通实现
1、图像文件类

class ImageFile {  
    private String name;  
    public ImageFile(String name) {  
        this.name = name;  
    }  
  
    public void display() { //展示文件内容 
        System.out.println("----图像文件'" + name + "展示");  
    }  
}  

2、文本文件类

class TextFile {  
    private String name;  
    public TextFile(String name) {  
        this.name = name;  
    }  
  
    public void display() { //展示文件内容 
        System.out.println("----文本文件'" + name + "'展示");  
    }  
}  

3、文件夹类

class Folder {  
    private String name;  
    //定义集合folderList,用于存储Folder类型的成员  
    private ArrayList folderList = new ArrayList();  
    //定义集合imageList,用于存储ImageFile类型的成员  
    private ArrayList imageList = new ArrayList();  
    //定义集合textList,用于存储TextFile类型的成员  
    private ArrayList textList = new ArrayList();  
      
    public Folder(String name) {  
        this.name = name;  
    }  
      
    //增加新的Folder类型的成员  
    public void addFolder(Folder f) {  
        folderList.add(f);  
    }  
      
    //增加新的ImageFile类型的成员  
    public void addImageFile(ImageFile image) {  
        imageList.add(image);  
    }  
      
    //增加新的TextFile类型的成员  
    public void addTextFile(TextFile text) {  
        textList.add(text);  
    }  
          
    //需提供三个不同的方法removeFolder()、removeImageFile()和removeTextFile()来删除成员,代码省略  
    //需提供三个不同的方法getChildFolder(int i)、getChildImageFile(int i)和getChildTextFile(int i)来获取成员,代码省略  
  
    public void display() {  
        System.out.println("****文件夹'" + name + "'展示");  
          
        //如果是Folder类型的成员,递归调用Folder的display()方法  
        for(Object obj : folderList) {  
            ((Folder)obj).display();  
        }  
          
        //如果是ImageFile类型的成员,调用ImageFile的display()方法  
        for(Object obj : imageList) {  
            ((ImageFile)obj).display();  
        }  
          
        //如果是TextFile类型的成员,调用TextFile的display()方法  
        for(Object obj : textList) {  
            ((TextFile)obj).display();  
        }  
    }   
}

4、存在的问题
(1) 文件夹类Folder的设计和实现都非常复杂,需要定义多个集合存储不同类型的成员,而且需要针对不同的成员提供增加、删除和获取等管理和访问成员的方法,存在大量的冗余代码,系统维护较为困难;
(2) 由于系统没有提供抽象层,客户端代码必须有区别地对待充当容器的文件夹Folder和充当叶子的ImageFile和TextFile,无法统一对它们进行处理;
(3) 系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改,例如如果需要在系统中增加一种新类型的视频文件VideoFile,则必须修改Folder类的源代码,否则无法在文件夹中添加视频文件。

三、组合模式
从上面的代码中可以看出,我们分别定义了图片文件类、文本文件类与文件夹类,这是因为文件与文件夹之间的操作不同,文件没有下级节点,而文件夹可以有下级节点,但是可以这样想:既然文件与目录都是可以作为一个节点的下级节点而存在,那么就可以将二者抽象为一类对象,虽然二者的操作不同,但是可以在实现类的方法中具体定义,比如文件没有新增下级节点的方法,就可以在文件的这个方法中抛出一个异常,不做具体实现,而在文件夹中则具体实现新增操作。而二者都有的功能,就可以各自实现。
1、定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
2、类图


设计模式--组合模式_第2张图片
image.png

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
如果不使用组合模式,客户端代码将过多地依赖于容器对象复杂的内部实现结构,容器对象内部实现结构的变化将引起客户代码的频繁变化,带来了代码维护复杂、可扩展性差等弊端。组合模式的引入将在一定程度上解决这些问题。

四、运用组合模式
1、构建Component的抽象类

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();  //业务方法--对应上例为display()  
}

2、文件(叶子)继承Component

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

3、文件夹(容器)继承Component

class Composite extends Component {  
    private ArrayList 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 (Component)list.get(i);  
    }  
    public void operation() {  
        //容器的具体业务--display()  
        //递归调用成员构件的业务方法  
        for(Object obj:list) {  
            ((Component)obj).operation();  
        }  
    }     
}

在容器类中实现了在抽象类中声明的所有方法,既包括业务方法,也包括用于访问和管理其子成员的方法,如add()、remove()和getChild()等方法。在实现具体业务方法时,由于容器类充当的是容器角色,包含成员类,因此它将调用其成员类的业务方法。在组合模式结构中,由于容器类中仍然可以包含容器构件,因此在对容器类进行处理时需要使用递归算法,即在容器类的operation()方法中递归调用其成员类的operation()方法。

五、组合模式的分类
1、透明组合模式
上面这种组合模式也称为透明组合模式,抽象类Component中声明了所有用于管理成员对象的方法,包括add()、remove()以及getChild()等方法,这样做的好处是确保所有的类都有相同的接口。叶子与容器所提供的方法是一致的,客户端可以相同地对待所有的类。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点:不安全,因为叶子和容器在本质上是有区别的。叶子不可能有下一个层次的对象,不可能包含成员,因此为其提供add()、remove()以及getChild()等方法是没有意义的,如果没有提供相应的错误处理代码,虽然在编译阶段不会出错,但在运行阶段调用这些方法就会出错。


设计模式--组合模式_第3张图片
image.png

2、安全组合模式
在抽象类Component中不声明任何用于管理成员对象的方法(add()、remove()以及getChild()等),只声明叶子和容器共同需要的方法,在容器类中声明只有他需要的方法。这种做法是安全的。


设计模式--组合模式_第4张图片
image.png

安全组合模式的缺点是不够透明,因为叶子和容器具有不同的方法,且容器中那些用于管理成员对象的方法没有在抽象类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子和容器。

六、组合模式在Android中的应用
View和ViewGroup是一种很标准的组合模式:


设计模式--组合模式_第5张图片
image.png

七、几种模式的区别


设计模式--组合模式_第6张图片
image.png

你可能感兴趣的:(设计模式--组合模式)