【趣味設計模式系列】之【組合模式】

小豬爸爸發表於2022-03-02

1. 簡介

組合模式(Composite Pattern):將物件組合成樹形結構以表示部分-整體的層次關係。

2. 示例

假設要設計一個檔案系統的目錄,需要靈活的在某個目錄下新增、刪除目錄或檔案,統計指定目錄下的檔案個數,計算指定目錄下的檔案大小。

設計類圖如下:

抽象類Node

package com.wzj.composite;

/**
 * @Author: wzj
 * @Date: 2020/9/23 15:33
 * @Desc:
 */
public abstract class Node {

    //檔案路徑
    protected String path;

    public Node(String path) {
        this.path = path;
    }

    public String getPath() {
        return path;
    }

    // 統計目錄下檔案數目
    public abstract int countNumOfFiles();

    // 統計目錄下檔案大小
    public abstract long countSizeOfFiles();

    // 列印路徑
    public abstract void print();

}

檔案類FileNode

package com.wzj.composite;

import java.io.File;

/**
 * @Author: wzj
 * @Date: 2020/9/23 16:38
 * @Desc:
 */
public class FileNode extends Node {
    public FileNode(String path) {
        super(path);
    }

    @Override
    public String getPath() {
        return super.getPath();
    }

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

    @Override
    public long countSizeOfFiles() {
        File file = new File(path);
        if (!file.exists()) return 0;
        return file.length();
    }


    @Override
    public void print() {
        System.out.println(path);
    }
}

目錄類DirectorNode

package com.wzj.composite;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: wzj
 * @Date: 2020/9/23 16:48
 * @Desc:
 */
public class DirectoryNode extends Node{
    public List<Node> list = new ArrayList<>();
    public DirectoryNode(String path) {
        super(path);
    }

    @Override
    public String getPath() {
        return super.getPath();
    }

    @Override
    public int countNumOfFiles() {
        int num = 0;
        for (Node node : list) {
            num += node.countNumOfFiles();
        }
        return num;
    }

    @Override
    public long countSizeOfFiles() {
        long size = 0;
        for (Node node : list) {
            size += node.countSizeOfFiles();
        }
        return size;
    }

    @Override
    public void print() {
        System.out.println(path);
    }

    public void addSubNode(Node node) {
        list.add(node);
    }

    public void removeSubNode(Node node) {
        int size = list.size();
        int i = 0;
        for (; i < size; ++i) {
            if (list.get(i).getPath().equalsIgnoreCase(node.getPath())) {
                break;
            }
        }

        if (i < size) {
            list.remove(i);
        }
    }

}

客戶端Client

package com.wzj.composite;

/**
 * @Author: wzj
 * @Date: 2020/9/23 20:44
 * @Desc:
 */
public class Client {
    public static void main(String[] args) {
        DirectoryNode root = new DirectoryNode("root");
        DirectoryNode chapter1 = new DirectoryNode("chapter1");
        DirectoryNode chapter2 = new DirectoryNode("chapter2");

        Node r1 = new FileNode("r1.txt");
        Node c11 = new FileNode("c11.txt");
        Node c12 = new FileNode("c12.txt");

        DirectoryNode b21 = new DirectoryNode("section21");

        Node c211 = new FileNode("c211.txt");
        Node c212 = new FileNode("c212.txt");

        root.addSubNode(chapter1);
        root.addSubNode(chapter2);
        root.addSubNode(r1);

        chapter1.addSubNode(c11);
        chapter1.addSubNode(c12);
        chapter2.addSubNode(b21);

        b21.addSubNode(c211);
        b21.addSubNode(c212);

        printTree(root, 0);

        System.out.println("root files num:" + root.countNumOfFiles());
        System.out.println("/root/chapter1/ files num:" + chapter2.countNumOfFiles());

    }

    // 列印樹狀結構
    public static void printTree(Node root, int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("--");
        }
        root.print();
        if(root instanceof DirectoryNode) {
            for (Node n : ((DirectoryNode)root).list) {
                printTree(n, depth + 1);
            }
        }
    }
}

結果

root
--chapter1
----c11.txt
----c12.txt
--chapter2
----section21
------c211.txt
------c212.txt
--r1.txt
root files num:5
/root/chapter1/ files num:2

3. 原始碼分析

SpringMVC中對引數的解析使用的是HandlerMethodArgumentResolver介面,該類有一個實現類為HandlerMethodArgumentResolverComposite,為組合類,又持有其他HandlerMethodArgumentResolver物件,在它的實現方法中是對其他組合模式中的節點進行迴圈處理,從而選擇最適合的一個。

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

        // 對引數解析器的引用
	private final List<HandlerMethodArgumentResolver> argumentResolvers =
			new LinkedList<HandlerMethodArgumentResolver>();

        // 對其所擁有的物件迴圈,找到最適合的引數解析器
        private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
				if (logger.isTraceEnabled()) {
					logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
							parameter.getGenericParameterType() + "]");
				}
				if (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

4. 總結

4.1 優點

  • 高層呼叫簡單
    一棵樹形機構中的所有節點都是Component,區域性和整體對呼叫者來說沒有任何區別,也就是說,高層模組不必關心自己處理的是單個物件還是整個組合結構,簡化了高層模組的程式碼。
  • 節點自由增加
    使用了組合模式後,我們可以看看,如果想增加一個樹枝節點、樹葉節點是不是都很容易,只要找到它的父節點就成,非常容易擴充套件,符合開閉原則,對以後的維護非常有利。

4.2 缺點

組合模式不容易限制組合中的構件。

相關文章