设计模式学习笔记(十一)组合模式

本文详细介绍了组合设计模式的概念、示例应用,区分了透明组合与安全组合的区别,以及其在组织机构、文件系统中的应用,强调了模式的优点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

组合模式是一种结构型设计模式,它提供了一种方式来将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

在组合模式中,有一个根对象(Component),它包含了一系列的子对象(SubObject)。根对象负责管理其所有子对象的状态,并且定义了访问这些子对象的方法。当需要添加或删除子对象时,根对象会相应地进行调整。

使用组合模式的好处在于,它可以将复杂的对象结构分解为简单的组成部分,并且可以轻松地添加、删除或替换这些组成部分。此外,由于子对象是作为根对象的成员存在的,因此可以方便地对它们进行操作和管理。
组合模式为处理树形结构提供了一种较为完美的解决方案,它描述了如何将容器和叶子进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器和叶子。

在这里插入图片描述
● Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
● Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
● Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

示例

示例一

我们用公司的组织机构来举一个例子,公司的各层机构作为容器,人员作为叶子节点
1、定义抽象构件

/**
 * 抽象组织机构:抽象构件
 * */
abstract class Component {
	public abstract void add(Component c) throws Exception; //增加成员
	public abstract void remove(Component c) throws Exception; //删除成员
	public abstract Component getChild(int i); //获取成员
	public abstract void getName(String orgnaName);  //业务方法 获取用户名
}

2、定义容器构件

/**
 * 组织机构容器:继承抽象构件
 * */
public class Orgna extends Component{
    List<Component> componentList = new ArrayList<>();
    private String orgnaName;
    public Orgna(String orgnaName) {
        this.orgnaName = orgnaName;
    }
    @Override
    public void add(Component c) throws Exception {
        componentList.add(c);
    }
    @Override
    public void remove(Component c) throws Exception {
        componentList.remove(c);
    }
    @Override
    public Component getChild(int i) {
        return componentList.get(i);
    }
    @Override
    public void getName(String orgnaName) {
        for (Component obj : componentList) {
            obj.getName(this.orgnaName);
        }
    }
}

3、定义叶子节点

/**
 * 员工作为叶子节点
 * */
public class staffLeaf extends Component{
    private String name;
    public staffLeaf(String name){
        this.name = name;
    }
    @Override
    public void add(Component c) throws Exception {
        throw new Exception();
    }
    @Override
    public void remove(Component c) throws Exception {
        throw new Exception();
    }
    @Override
    public Component getChild(int i) {
        return null;
    }
    @Override
    public void getName(String orgnaName) {
        System.out.println( orgnaName + " 职位的员工:" + name);
    }
}

4、客户端

 Component orgna,orgna1,orgna2,orgna3,staffLeaf1,staffLeaf2,staffLeaf3,staffLeaf4,staffLeaf5;

        orgna = new Orgna("公司");
        orgna1 = new Orgna("董事会");
        orgna2 = new Orgna("经理");
        orgna3 = new Orgna("普通员工");

        staffLeaf1 = new staffLeaf("张三");
        staffLeaf2 = new staffLeaf("李四");
        staffLeaf3 = new staffLeaf("王五");
        staffLeaf4 = new staffLeaf("王六");
        staffLeaf5 = new staffLeaf("王七");

        orgna.add(orgna1);
        orgna.add(orgna2);
        orgna.add(orgna3);
        orgna1.add(staffLeaf1);
        orgna2.add(staffLeaf2);
        orgna3.add(staffLeaf3);
        orgna3.add(staffLeaf4);
        orgna3.add(staffLeaf5);

        orgna.getName("公司");

输出结果

董事会 职位的员工:张三
经理 职位的员工:李四
普通员工 职位的员工:王五
普通员工 职位的员工:王六
普通员工 职位的员工:王七

示例二

同样的机构,我们这次不区分叶子节点还是容器节点

/**
 * 抽象组织机构:抽象构件
 * */
abstract class ComponentNew {
	public abstract void add(ComponentNew c) throws Exception; //增加成员
	public abstract void remove(ComponentNew c) throws Exception; //删除成员
	public abstract ComponentNew getChild(int i); //获取成员
	public abstract void getName();  //业务方法 获取用户名
}

/**
 * 组织机构容器:继承抽象构件
 * */

public class OrgnaNew extends ComponentNew {
    List<ComponentNew> componentList = new ArrayList<>();
    private String orgnaName;
    private Integer orgnaLevel;

    public OrgnaNew(String orgnaName,Integer orgnaLevel) {
        this.orgnaName = orgnaName;
        this.orgnaLevel = orgnaLevel;
    }
    @Override
    public void add(ComponentNew c) throws Exception {
        componentList.add(c);
    }

    @Override
    public void remove(ComponentNew c) throws Exception {
        componentList.remove(c);
    }

    @Override
    public ComponentNew getChild(int i) {
        return componentList.get(i);
    }

    @Override
    public void getName() {
        System.out.println("机构等级:" + orgnaLevel + ";机构名称:" + orgnaName);
        for (ComponentNew obj : componentList) {
            obj.getName();
        }
    }
}

客户端

ComponentNew orgna,orgna1,orgna2,orgna3,orgna1_1,orgna2_1,orgna3_1,orgna3_2;

        orgna = new OrgnaNew("1000",1);
        orgna1 = new OrgnaNew("1100",2);
        orgna2 = new OrgnaNew("1200",2);
        orgna3 = new OrgnaNew("1300",2);
        orgna1_1 = new OrgnaNew("1101",3);
        orgna2_1 = new OrgnaNew("1201",3);
        orgna3_1 = new OrgnaNew("1301",3);
        orgna3_2 = new OrgnaNew("1302",3);

        orgna.add(orgna1);
        orgna.add(orgna2);
        orgna.add(orgna3);
        orgna1.add(orgna1_1);
        orgna2.add(orgna2_1);
        orgna3.add(orgna3_1);
        orgna3.add(orgna3_2);

        orgna.getName();

输出结果

机构等级:1;机构名称:1000
机构等级:2;机构名称:1100
机构等级:3;机构名称:1101
机构等级:2;机构名称:1200
机构等级:3;机构名称:1201
机构等级:2;机构名称:1300
机构等级:3;机构名称:1301
机构等级:3;机构名称:1302

示例三

当我们扫描文件时,文件夹可以作为容器节点,具体的文件比如图片、文本可以作为叶子节点,使用组合模式从跟目录递归遍历这些文件。

//抽象文件类:抽象构件
abstract class AbstractFile {
	public abstract void add(AbstractFile file);
	public abstract void remove(AbstractFile file);
	public abstract AbstractFile getChild(int i);
	public abstract void scanFile();
}
 
//图像文件类:叶子构件
class ImageFile extends AbstractFile {
	private String name;
	
	public ImageFile(String name) {
		this.name = name;
	}
	
	@Override
	public void add(AbstractFile file) {
	   throw new Exception();
	}
	
	@Override
	public void remove(AbstractFile file) {
		throw new Exception();
	}
	
	@Override
	public AbstractFile getChild(int i) {
		throw new Exception();
		return null;
	}
	
	@Override
	public void scanFile() {
		//模拟扫描
		System.out.println("-->对图像文件'" + name + "'进行扫描");
	}
}
 
//文本文件类:叶子构件
class TextFile extends AbstractFile {
	private String name;
	
	public TextFile(String name) {
		this.name = name;
	}
	
	@Override
	public void add(AbstractFile file) {
	   throw new Exception();
	}
	
	@Override
	public void remove(AbstractFile file) {
		throw new Exception();
	}
	
	@Override
	public AbstractFile getChild(int i) {
		throw new Exception();
		return null;
	}
	
	@Override
	public void scanFile() {
		//模拟扫描
		System.out.println("-->对文本文件'" + name + "'进行扫描");
	}
}
 
//视频文件类:叶子构件
class VideoFile extends AbstractFile {
	private String name;
	
	public VideoFile(String name) {
		this.name = name;
	}
	
	@Override
	public void add(AbstractFile file) {
	   throw new Exception();
	}
	
	@Override
	public void remove(AbstractFile file) {
		throw new Exception();
	}
	
	@Override
	public AbstractFile getChild(int i) {
		throw new Exception();
		return null;
	}
	
	@Override
	public void scanFile() {
		//模拟扫描
		System.out.println("-->对视频文件'" + name + "'进行扫描");
	}
}
 
//文件夹类:容器构件
class Folder extends AbstractFile {
	//定义集合fileList,用于存储AbstractFile类型的成员
	private ArrayList<AbstractFile> fileList=new ArrayList<AbstractFile>();
	private String name;
		
	public Folder(String name) {
		this.name = name;
	}
	
	@Override
	public void add(AbstractFile file) {
	   fileList.add(file);	
	}
	
	@Override
	public void remove(AbstractFile file) {
		fileList.remove(file);
	}
	
	@Override
	public AbstractFile getChild(int i) {
		return (AbstractFile)fileList.get(i);
	}
	
	@Override
	public void scanFile() {
		System.out.println("-------对文件夹'" + name + "'进行扫描");  //模拟扫描
		
		//递归调用成员构件的scanFile()方法
		for(Object obj : fileList) {
			((AbstractFile)obj).scanFile();
		}
	}
}

客户端

//针对抽象构件编程
		AbstractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;
		
		folder1 = new Folder("根目录文件夹");
		folder2 = new Folder("图像文件");
		folder3 = new Folder("文本文件");
		folder4 = new Folder("视频文件");
		
		file1 = new ImageFile("林俊杰.jpg");
		file2 = new ImageFile("张杰.gif");
		file3 = new TextFile("斗罗大陆.txt");
		file4 = new TextFile("java开发手册.doc");
		file5 = new VideoFile("从你的全世界路过.rmvb");
 
		folder2.add(file1);
		folder2.add(file2);
		folder3.add(file3);
		folder3.add(file4);
		folder4.add(file5);
		folder1.add(folder2);
		folder1.add(folder3);
		folder1.add(folder4);
		
        //从“根目录文件夹”节点开始进行扫描文件
		folder1.scanFile();

输出结果

-------对文件夹'根目录文件夹'进行扫描
-------对文件夹'图像文件'进行扫描
-->对图像文件'林俊杰.jpg'进行扫描
-->对图像文件'张杰.gif'进行扫描
-------对文件夹'文本文件'进行扫描
-->对文本文件'斗罗大陆.txt'进行扫描
-->对文本文件'java开发手册.doc'进行扫描
-------对文件夹'视频文件'进行扫描
-->对视频文件'从你的全世界路过.rmvb'进行扫描

Process finished with exit code 0

透明组合模式与安全组合模式

通过以上两个例子我们可以看出,无论是子节点还是容器节点,都有add,remove等方法,而子节点是不需要这些方法的,如果子节点调用了这些方法要么报错要么抛出异常,因此我们要区分一下透明组合模式与安全组合模式。

一、透明组合模式

上述两个示例为透明组合模式

二、安全组合模式

安全组合模式有两种处理方案

方案1

在子节点中保留操作方法,抽象组合类则预先实现这些方法

//提供默认实现的抽象构件类
abstract class AbstractFile {
	public void add(AbstractFile file) {
		throw new Exception();
	}
	public void remove(AbstractFile file) {
		throw new Exception();
	}
	public AbstractFile getChild(int i) {
		throw new Exception();
		return null;
	}
	public abstract void scanFile();
}

另外,客户端在使用时,调用子节点时可以直接定义子节点的类,这样可以避免开发人员在使用时误认为是容器节点

		ImageFile file1,file2;
		TextFile file3,file4;
		VideoFile file5;
		AbstractFile folder1,folder2,folder3,folder4;

方案2

我们可以在抽象组合类中只保留操作方法

abstract class AbstractFile {
	public abstract void scanFile();
}

容器节点中单独编写add、remove等方法,子节点则只实现scanFile方法即可,这样在客户端使用时,可以分别定义抽象组合类容器节点

		AbstractFile file1,file2,file3,file4,file5;
		Folder folder1,folder2,folder3,folder4; //不能透明处理容器构件

这样的话,开发人员在使用时,叶子节点就无法调用到多余的方法。

总结

优点:
1、简化客户端代码:使用组合模式,客户端可以以统一的方式处理单个对象和组合对象,无需区分它们的差异,从而简化了客户端的代码。
2、增强扩展性:组合模式使得在对象树中添加新对象变得简单,可以通过继承组合模式的抽象类来实现。这样可以方便地扩展对象的层次结构,而无需修改现有代码。
3、统一操作:组合模式使得可以对组合对象和叶子对象执行相同的操作,从而统一了操作接口。客户端无需关心是对单个对象还是组合对象进行操作,简化了代码的逻辑。
4、简化管理对象集合:组合模式提供了一种树形结构来管理对象集合,使得对集合中的对象进行增加、移除和遍历等操作变得更加简单和直观。

缺点:
1、可能会导致系统过于一般化:使用组合模式时,对象的层次结构可能变得过于抽象和一般化,可能会导致某些特定操作变得困难或复杂。
2、难以限制组合对象的类型:组合模式通常会将组合对象和叶子对象抽象成一个类,这意味着在对象树中可以任意组合各种类型的对象。这可能导致某些操作在特定类型的对象上是不适用的,需要额外的处理来限制对象的类型。

适用场景:
1、树形结构:当对象集合具有层次结构,例如文件系统的文件和文件夹,菜单和子菜单等,可以使用组合模式进行管理。
2、部分-整体关系:当希望以统一的方式处理单个对象和组合对象时,可以使用组合模式。例如,对于组织机构中的公司、部门和员工,可以使用组合模式进行管理。
3、递归结构:当对象的结构可以递归定义为相似的对象组合时,可以使用组合模式。这样可以简化代码,提高扩展性,并使得操作可以在整个对象树上执行。
4、统一操作:当希望对组合对象和叶子对象进行一致的操作时,可以使用组合模式。例如,对于一个文件夹中的所有文件,可以通过组合模式实现一次性的遍历、搜索或执行操作。

总之,组合模式适用于具有层次结构的对象集合,并且需要以统一的方式处理单个对象和组合对象的场景。它可以简化代码、增强扩展性,并使得操作可以在整个对象树上执行。但需要注意,过度使用组合模式可能导致系统过于一般化和复杂化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值