@Author:云都小生
概述
组合模式其实就是把原本区分开了两种对象,以同一种方式进行处理。在组合模式中,有树枝(结点)和叶子两种对象,就像文件夹与文件一样。虽然它们是不同的对象,但是我们有时候就是想对它们的处理保持一致。
如果我们对树枝和不同的叶子进行不同的处理,想必需要会增加大量冗余的代码,同时,系统的灵活性和可扩展性会降低。
就像上面说到的文件夹与不同的文件,不同的文件可以是exe、css、html,如果我们区别对待,那在客户端中,我们就会有更多冗杂的代码。
在组合模式中,有以下这些角色:
抽象构件:它包含树枝和各种叶子,是组合模式中最关键的构件;
叶子构件:在组合结构中,它是末端了,它不像节点一样有自己的下属;
容器构件:类似文件夹,在它下面可能还有容器构件和叶子。
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。
实例场景
现在有这么一种情况,我们要对对文件夹和不同的文件进行不同的处理,例如文件夹加上f.的前缀,然后exe文件加上e.前缀,txt文件加上t.前缀。
通常我们可能会这样设计,将处理文件夹、exe、txt文件的方式区分开,然后让客户端去选,这样无疑增加了客户端的复杂度。所以,我们要来使用组合模式。
代码实现
/* 抽象类(关键类) */
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(); //业务方法
}
/* 文件夹(结点、树枝) */
import java.util.ArrayList;
public class Folder extends Component {
String name;
private ArrayList<Component> components = new ArrayList<Component>();
Folder(String name){
this.name = name;
}
public void add(Component component) {
components.add(component);
}
public void remove(Component component) {
components.remove(component);
}
public Component getChild(int i) {
return (Component)components.get(i);
}
public void operation() {
System.out.println("已经对" + name + "文件夹进行处理," + "同时,对文件夹内的文件进行处理。");
for(Object obj:components)
{
((Component)obj).operation();
}
}
}
/* exe文件(树叶) */
public class Exe extends Component {
String name;
Exe(String name){
this.name = name;
}
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("已经对" + name + ".exe文件进行处理。");
}
}
/* excel(树叶) */
public class Excel extends Component{
String name;
Excel(String name){
this.name = name;
}
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("已经对" + name + ".excel文件进行处理。");
}
}
/* 测试类 */
public class Client {
public static void main(String[] args) {
Folder folder1 = new Folder("文件夹1");
Folder folder2 = new Folder("文件夹2");
folder1.add(new Exe("测试1"));
folder1.add(new Exe("测试2"));
folder2.add(new Excel("测试3"));
folder2.add(new Excel("测试4"));
folder1.operation();
folder2.operation();
}
}
关键点有两点,第一点就是最上面的抽象类,对树枝和树叶进行了统一的处理。第二点是树枝(Folder),它还能包容其他树枝(Folder)和文件(exe、excel),所以需要有一个ArrayList来存放。
代码优化
现在有这么一个问题,对于现在的程序来说,我增加多处理一种文件(.html),确实很方便,在不修改源代码的情况下,我可以直接增加一个类。但是,我每增加一个,就得覆盖之前那个抽象类的方法,每一次方法都要提示“不支持这个方法”,是不是很麻烦?
有个办法可以解决。
abstract class 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 abstract void operation();
}
这样一来,就只有文件夹类(树枝)需要覆盖,工作量少了一些。
第二种方式,你可以直接就把抽象类这样定义。
abstract class Component {
public void add(Component c) {
public abstract void operation();
}
简单粗暴。两种方法各有利弊,第一种又叫透明组合方法,第二种又叫安全组合方法。第一种的缺点是不安全,很可能会因为”树叶”调用了这些方法而出错。第二种方法的缺点是,脱离了针对抽象编程的初衷。
后话
使用组合模式,我们可以一致地对待组合和单个对象,降低了客户端的复杂度。其次,可扩展性也很强,符合“开闭原则”。
缺点是,如果我们希望容器中只有特定类型的对象,由于它们继承自相同的抽象层,处理会相当的困难。
2017/11/10 21:49:01 @Author:云都小生