设计模式-组合模式及应用

324 阅读3分钟

组合模式在我们的生活场景中也有体现,比如说我们的公司组织结构,大的部门中间包含一个个一级部门,一级部门下还有不同的子部门。 或者说我们计算机中的文件系统,根目录下有各文件,文件夹下可以有子文件夹,也可以有文件。子文件夹下也可以有文件夹或者文件。类似于这种树形结构的业务架构,今天所说的组合模式能可好的解决此类问题。

组合模式的定义与结构

组合(Composite Pattern)模式的定义:将一组对象组织(Compose)成树形结构,以表示一种“部分-整体”的层次结构。使用户对单个对象和组合对象具有一致的访问性。

  1. 模式的结构 组合模式包含以下主要角色。
  • 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。
  • 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
  • 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含操作树枝构建的操作等方法。

组合模式结构图: 在这里插入图片描述

代码案例: 通过模拟文件系统,体会一下组合模式的使用;

  • 抽象构件 这里可以使用接口,或抽象类。
public abstract class FileSystemComponent {
	
	protected String path;
	
	public FileSystemComponent(String path) {
		this.path = path;
	}
	
	// 文件数量
	public abstract int countNumOfFiles();

	// 文件内容大小
	public abstract Long countSizeOfFiles();

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
	
	public abstract String show();
}
  • 树叶构件 文件类(在文件系统中,文件是最小单元)
public class FileLeaf extends FileSystemComponent{
	public FileLeaf(String path) {
		super(path);
	}

	@Override
	public int countNumOfFiles() {
		return 0;
	}

	@Override
	public Long countSizeOfFiles() {
		return 0L;
	}

	@Override
	public String show() {
		return "/"+path;	
	}
}
  • 树枝构件 文件夹(文件夹可以包含子文件夹和文件)
public class DirectoryComposite extends FileSystemComponent {

	private List<FileSystemComponent> subFileStemComponent = new ArrayList<FileSystemComponent>();

	public DirectoryComposite(String path) {
		super(path);
	}

	@Override
	public int countNumOfFiles() {
		int numOfFiles = 0;
		for (FileSystemComponent fileLeaf : subFileStemComponent) {
			numOfFiles += fileLeaf.countNumOfFiles();
		}
		return numOfFiles;
	}

	@Override
	public Long countSizeOfFiles() {
		Long sizeOfFiles = 0L;
		for (FileSystemComponent fileSystemComponent : subFileStemComponent) {
			sizeOfFiles +=  fileSystemComponent.countSizeOfFiles();
		}
		return sizeOfFiles;
	}
	
	
	public void addSubNode(FileSystemComponent fileSystemComponent) {
		subFileStemComponent.add(fileSystemComponent);
	}
	
	public void removeSubNode(FileSystemComponent fileSystemComponent) {
		int size = subFileStemComponent.size();
		//循环当前文件夹下的所有目录
		int i =0;
		for (; i < size; i++) {
			// 如果文件名相同 获取当前的坐标
			if (subFileStemComponent.get(i).getPath().equalsIgnoreCase(fileSystemComponent.getPath())) { 
				break;
			}
		}
		
	   //根据坐标删除文件
	  subFileStemComponent.remove(i);	
	}

	@Override
	public String show() {
		for (Object fileSystemComponent : subFileStemComponent) {
			System.out.println(path + ((FileSystemComponent)fileSystemComponent).show());
		}
		return "";
	}
}
  • client 使用方
public class Client {
	public static void main(String[] args) {
	// 模拟一下树形结构的管理
		// |- Desktop
		// |---------dir
		// |-------------test.txt
		// |-------------tmp
		// |----------------java.word
		 
		// 文件1
		FileSystemComponent text = new FileLeaf("test.txt");
		// 文件夹1
		DirectoryComposite dir = new DirectoryComposite("Desktop/dir");
		// 文件夹 Desktop/dir 下新增一个文件
		dir.addSubNode(text);
		// 文件夹2  Desktop/dir 下新增一个文件夹
		DirectoryComposite tmp = new DirectoryComposite("Desktop/dir/tmp"); 
		// 文件2
		FileSystemComponent test = new FileLeaf("java.word");
		tmp.addSubNode(test);
		// 文件夹 Desktop/dir 添加到文件夹 Desktop/dir 下
		dir.addSubNode(tmp);
		dir.show();
	}
}

执行结果:

 Desktop/dir/test.txt
 Desktop/dir/tmp/java.word
 Desktop/dir

案例结构图: 在这里插入图片描述

组合模式的优缺点

优点:

  • 组合模式使得使用方代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

缺点:

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

组合模式使用场景

其实组合模式更偏向于具体某一种场景的使用,也可以说是针对于树形这种结构的场景。所以它解决的问题也比较单一。

  • 在需要表示一个对象整体与部分的层次结构的场合。
  • 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

组合模式在源码中使用

1.组合模式在HashMap中的使用 先看一段案例代码

public class JDKUsage {
	
	public static void main(String[] args) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("a", 1);
		map.put("b", 2);
		
		Map<String, Integer> map1 = new HashMap<String, Integer>();
		map1.put("c", 3);
		map.putAll(map1);

		System.out.println(map);
	}
}

执行结果:{a=1, b=2, c=3} 不用多讲,都会使用Map的Api,那它底层是如何实现的,为什么说这里使用到了组合模式。

首先看下Hash的层次结构:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {}

继承AbstractMap<K,V>,实现Map<K,V>. 在这里插入图片描述

这里的Map<K,V>就是抽象构建角色,包含put、putAll等方法,而HashMap则为中间构件角色,所有的数据操作都是在Node节点,静态内部类的数组 Node<K,V>[] tab为叶子节点角色。

// node节点
static class Node<K, V> implements Map.Entry<K, V> {
    final int hash;
    final K key;
    V value;
    Node<K, V> next;
    Node(int hash, K key, V value, Node<K, V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    //略
    
    // 往节点set值 put、putAll最终都会调此方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K, V>[] tab;
        Node<K, V> p;
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K, V> e;
            K k;
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    //略
}

从HashMap案例中,可以看到设计模式其实提供的是一种软件设计思想,并没有完全安装标准的概念来。许多优秀的框架,也是基于好的设计模式,灵活设计模块的组件。