设计模式——组合模式


@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:云都小生

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值