乐趣区

关于设计模式:设计模式学习笔记十一组合模式

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 总结

退出移动版