首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 組合模式深度解析:樹形結構統一處理與遞歸操作實戰

     編輯:Java 相關知識

模式定義與核心價值

組合模式:將對象組合成樹形結構以表示「部分-整體」層次結構,使客戶端對單個對象和組合對象的使用具有一致性 (統一接口)。核心價值在於遞歸操作的統一抽象,讓客戶端無需區分葉子節點與容器節點。

結構角色

角色職責例子
Component (抽象構件)定義統一接口,聲明增刪改查子節點、操作方法FileSystemNode 接口
Leaf (葉子構件)無子節點,實現具體業務邏輯File 文件
Composite (容器構件)包含子節點列表,實現子節點管理,操作委託遞歸子節點Directory 目錄
Client (客戶端)通過 Component 接口操作樹結構文件掃描器、備份工具

經典實戰:文件系統掃描器

// 1. 抽象構件
interface FileSystemNode {
    String getName();
    long getSize();
    void printTree(String indent);
}

// 2. 葉子構件:文件
class FileNode implements FileSystemNode {
    private final String name;
    private final long size;
    
    public FileNode(String name, long size) {
        this.name = name;
        this.size = size;
    }
    
    @Override public String getName() { return name; }
    @Override public long getSize() { return size; }
    
    @Override
    public void printTree(String indent) {
        System.out.println(indent + "📄 " + name + " (" + formatSize(size) + ")");
    }
}

// 3. 容器構件:目錄
class DirectoryNode implements FileSystemNode {
    private final String name;
    private final List children = new ArrayList<>();
    
    public DirectoryNode(String name) { this.name = name; }
    
    public void add(FileSystemNode node) { children.add(node); }
    public void remove(FileSystemNode node) { children.remove(node); }
    public List getChildren() { return Collections.unmodifiableList(children); }
    
    @Override public String getName() { return name; }
    
    @Override
    public long getSize() {
        return children.stream().mapToLong(FileSystemNode::getSize).sum();
    }
    
    @Override
    public void printTree(String indent) {
        System.out.println(indent + "📁 " + name + " (" + formatSize(getSize()) + ")");
        for (FileSystemNode child : children) {
            child.printTree(indent + "  ");
        }
    }
}

// 4. 工具方法
private static String formatSize(long bytes) {
    if (bytes < 1024) return bytes + " B";
    if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
    return String.format("%.1f MB", bytes / (1024.0 * 1024));
}

// 5. 客戶端使用
public class FileScanner {
    public static void main(String[] args) {
        // 構建樹
        DirectoryNode root = new DirectoryNode("項目根目錄");
        DirectoryNode src = new DirectoryNode("src");
        DirectoryNode main = new DirectoryNode("main");
        DirectoryNode java = new DirectoryNode("java");
        
        java.add(new FileNode("Main.java", 2048));
        java.add(new FileNode("UserService.java", 5120));
        main.add(java);
        src.add(main);
        src.add(new FileNode("README.md", 1024));
        root.add(src);
        
        DirectoryNode test = new DirectoryNode("test");
        test.add(new FileNode("UserServiceTest.java", 3072));
        root.add(test);
        
        // 統一操作:打印樹、計算總大小
        root.printTree("");
        System.out.println("總大小: " + formatSize(root.getSize()));
    }
}

輸出效果

📁 項目根目錄 (11.3 KB)
  📁 src (8.2 KB)
    📁 main (7.0 KB)
      📁 java (7.0 KB)
        📄 Main.java (2.0 KB)
        📄 UserService.java (5.0 KB)
    📄 README.md (1.0 KB)
  📁 test (3.0 KB)
    📄 UserServiceTest.java (3.0 KB)
總大小: 11.3 KB

進階應用:操作分離 (訪問者模式結合)

// 為節點添加 accept 方法
interface FileSystemNode {
    void accept(FileVisitor visitor);
    // ... 其他方法
}

interface FileVisitor {
    void visit(FileNode file);
    void visit(DirectoryNode dir);
}

// 具體訪問器:查找大文件
class LargeFileFinder implements FileVisitor {
    private final long threshold;
    private final List result = new ArrayList<>();
    
    public LargeFileFinder(long thresholdKB) { this.threshold = thresholdKB * 1024; }
    
    @Override public void visit(FileNode file) {
        if (file.getSize() > threshold) result.add(file);
    }
    @Override public void visit(DirectoryNode dir) {
        for (FileSystemNode child : dir.getChildren()) child.accept(this);
    }
    public List getResult() { return result; }
}

// 使用
LargeFileFinder finder = new LargeFileFinder(4); // 4KB
root.accept(finder);
finder.getResult().forEach(f -> System.out.println("大文件: " + f.getName()));

JDK 內建組合模式應用

APIComponentLeafComposite
AWT/SwingComponentButton, Label, TextFieldContainer, Panel, JFrame
XML DOMNodeText, Comment, AttrElement, Document
NIO.2Path文件 Path目錄 Path (Files.walkFileTree)

適用性判斷與注意事項

  • ✅ 適用:樹形結構 (目錄、組織架構、菜單、AST、GUI 組件)
  • ✅ 適用:希望客戶端忽略層次差異,統一操作
  • ⚠️ 注意:不要 在 Leaf 中實現 add/remove 拋出異常,違反接口隔離原則 → 可將子節點管理移至單獨接口 ParentNode
  • ⚠️ 注意:深度遞歸可能導致 StackOverflowError,深層樹需改用迭代或尾遞歸優化
  • ⚠️ 注意:共享引用 (同一子節點加入多父節點) 會破壞樹結構,需防禦性拷貝或禁止

現代改良:函數式操作

// 使用 Stream API 替代顯式遞歸
public long calculateTotalSize() {
    return StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false)
        .mapToLong(FileSystemNode::getSize).sum();
}

// 深度優先迭代器 (避免棧溢出)
public Iterator iterator() {
    return new Iterator<>() {
        private final Deque> stack = new ArrayDeque<>();
        { stack.push(Collections.singletonList(this).iterator()); }
        
        @Override public boolean hasNext() {
            while (!stack.isEmpty()) {
                Iterator it = stack.peek();
                if (it.hasNext()) return true;
                stack.pop();
            }
            return false;
        }
        @Override public FileSystemNode next() {
            Iterator it = stack.peek();
            FileSystemNode node = it.next();
            if (node instanceof DirectoryNode dir) {
                stack.push(dir.getChildren().iterator());
            }
            return node;
        }
    };
}

總結

組合模式通過統一接口將樹形結構的遞歸操作封裝,讓客戶端代碼極其簡潔。核心在於 Composite 內部持有 List<Component> 並在操作方法中遞歸委託子節點。現代 Java 可結合 Stream API、迭代器模式、訪問者模式 進一步增強靈活性與性能。適用於一切「部分-整體」層次結構場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved