共计 6859 个字符,预计需要花费 18 分钟才能阅读完成。
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 实例
开发一个杀毒软件零碎,能够对某个文件夹或单个文件进行杀毒,还能依据文件类型的不同提供不同的杀毒形式,比方文本文件和图像文件的杀毒形式有所差别,应用组合模式对该零碎进行设计。
首先定义形象构件类 AbstractFile
,Folder
作为容器构件类,ImageFile
,TextFile
,VideoFile
作为叶子构件类,代码如下:
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 总结