DesignPattern - 组合模式【结构型】

421 阅读4分钟

欢迎关注微信公众号:FSA全栈行动 👋

一、组合模式介绍

组合模式又叫部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构,可以更好的实现管理操作;组合模式使得用户可以使用一致的方法操作单个对象和组合对象;部分-整体对象的基本操作多数是一样的,但是也有不一样的地方。

本质:组合模式可以使用一棵树来表示

  • 核心组成

    • 组合中的对象声明接口(Component):在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 子部件。

    • 容器对象(Composite):定义有枝节点行为,用来存储子部件,在 Component 接口中实现与子部件有关的操作,如增加和删除等。

    • 叶子对象(Leaf):没有子节点。

    注意:容器对象和叶子节点都实现 Component 接口,这也是能够将两者一致对待的关键所在。

  • 应用场景

    • 银行总行,总行有前台、后勤、网络部门等,辖区下还有地方分行,也有前台、后勤、网络部门,最小的分行就没有子分行了
    • 公司也是,总公司下有子公司,每个公司大部分的部门都类似
    • 文件夹和文件,也有层级管理关系
  • 何时使用

    • 当想表达对象的部分-整体的层次结构
    • 当我们要处理的对象可以生成一颗树形结构,对树上节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
  • 优点

    • 客户只需要面对一致的对象而不用考虑整体部分或者节点叶子
    • 方便创建出复杂的层次结构
  • 缺点

    • 客户端需要花更多时间理清类之间的层次关系

二、组合模式代码实现

以 "文件-文件夹" 为例,在 Linux 系统中,两者均是文件,定义一个文件接口:

说明 :Component 接口定义一些通用属性及方法(还可以增加一些默认行为),故使用抽象类。除了 display() 方法以外,还可以定义诸如 rename()delete() 等 文件 和 文件夹 都可以有的方法,当然这类方法对于文件和文件夹而言是不一样的,文件的 delete() 是删除自己,而文件夹的 delete() 则可以是删除整个文件目录。

/**
 * 组合中的对象声明接口(声明一个接口用于访问和管理子部件)
 *
 * @author GitLqr
 */
public abstract class File {

	String name;

	public File(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 显示子部件信息
	 */
	public abstract void display();
}

定义容器对象(文件夹)类:

说明 :因为是容器对象,需要定义容器(一般是数组)来存储子部件,以及增加必要的管理子部件的方法,如:增加、删除。

/**
 * 容器对象(定义枝节点行为,存储子部件):文件夹
 *
 * @author GitLqr
 */
public class Folder extends File {

	private List<File> files = new ArrayList<>();

	public Folder(String name) {
		super(name);
	}

	/**
	 * 浏览文件夹中的文件
	 */
	@Override
	public void display() {
		for (File file : files) {
			file.display();
		}
	}

	/**
	 * 向文件夹中添加文件
	 */
	public void addFile(File file) {
		files.add(file);
	}

	/**
	 * 从文件夹中删除文件
	 */
	public void removeFile(File file) {
		files.remove(file);
	}
}

定义叶子对象(具体文件)类:

说明:叶子对象没有子节点,所以不需要像文件夹那样,只需要实现 Component 接口中的方法即可。

/**
 * 叶子节点:文本文件
 *
 * @author GitLqr
 */
public class TextFile extends File {

	public TextFile(String name) {
		super(name);
	}

	@Override
	public void display() {
		System.out.println("文本文件 - 文件名为:" + name);
	}

}
/**
 * 叶子节点:图像文件
 *
 * @author GitLqr
 */
public class ImageFile extends File {

	public ImageFile(String name) {
		super(name);
	}

	@Override
	public void display() {
		System.out.println("图像文件,文件名:" + name);
	}

}

使用:

说明:display() 方法对于文件夹【容器对象】而言,是显示容器中所有 "文件" 的信息,对于具体文件【叶子对象】而言,则是显示自己的信息。

public static void main(String[] args) {
    Folder folder1 = new Folder("文件夹1");

    // 填充文件夹2
    Folder folder2 = new Folder("文件夹2");
    TextFile readmeFile = new TextFile("readme.txt");
    ImageFile qrcodeFile = new ImageFile("qrcode.jpg");
    ImageFile pictureFile = new ImageFile("picture.jpg");
    folder2.addFile(readmeFile);
    folder2.addFile(qrcodeFile);
    folder2.addFile(pictureFile);

    // 把文件夹2添加到文件夹1中
    folder1.addFile(folder2);
    // "递归"遍历文件夹1中所有的文件
    folder1.display();
}

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~