组合模式 logo

组合模式

Organization
microwind
组合模式

以树形结构组织对象,使客户端可以统一处理个体对象和组合对象。

Overview

Publishermicrowind
Repositoryai-skills
Skill name组合模式
Stars
93
Forks
25
Bundled files
3
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 3 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by microwind on GitHub. Read the source before you install it.

Installation

Install the 组合模式 AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/microwind/ai-skills/tree/main/design-patterns/composite-pattern
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/microwind/ai-skills.git /tmp/ai-skills
mkdir -p .claude/skills
cp -r /tmp/ai-skills/design-patterns/composite-pattern .claude/skills/microwind-26
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable 组合模式 in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads 组合模式 on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so 组合模式 is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

组合模式 (Composite Pattern)

概述

组合模式将对象组织成树形结构以表示"部分-整体"的层次关系,允许客户端以统一的方式处理个别对象和对象的组合。通过消除客户端对具体类型的依赖,让代码更灵活、可维护。

核心原则:

  • 🌳 递归组合: 容器可包含容器,形成任意深度的树
  • 🔗 部分-整体一致: 客户端统一处理部分和整体
  • 👁️ 透明性: 客户端不需区分是叶子还是容器

何时使用

完美适用场景

条件说明优先级
层次结构系统存在明显的父子关系树⭐⭐⭐
统一操作个体和整体操作相同⭐⭐⭐
递归组合容器可包含容器⭐⭐⭐
灵活增长树的结构经常变化⭐⭐
多种遍历需要多种树遍历方式⭐⭐

触发信号

✅ "如何统一处理树形结构中的所有类型?" ✅ "对象的集合可以递归嵌套吗?" ✅ "操作是否对叶子和容器都一样?" ✅ "是否需要支持任意深度的嵌套?" ✅ "能否使用简单的递归逻辑处理?" ✅ "需要在树中添加/删除节点吗?"

典型应用场景

场景叶子容器操作示例优先级
文件系统FileDirectory列表/删除/权限⭐⭐⭐
DOM树TextNodeElement查询/样式/事件⭐⭐⭐
菜单系统MenuItemMenu导航/渲染/权限⭐⭐⭐
组织结构EmployeeDepartment统计/权限/工资⭐⭐
表达式树LiteralBinaryOp计算/优化/转换⭐⭐
GUI组件ButtonPanel布局/绘制/事件⭐⭐

4个实现方法

方法1: 透明组合 (容器叶子接口相同)

java
public interface Component {
    void add(Component child);
    void remove(Component child);
    void operation();
}

public class Leaf implements Component {
    private String name;
    public Leaf(String name) { this.name = name; }
    
    @Override
    public void add(Component child) {} // 空操作
    
    @Override
    public void remove(Component child) {}
    
    @Override
    public void operation() { System.out.println(name); }
}

public class Composite implements Component {
    private String name;
    private List<Component> children = new ArrayList<>();
    public Composite(String name) { this.name = name; }
    
    @Override
    public void add(Component child) { children.add(child); }
    
    @Override
    public void remove(Component child) { children.remove(child); }
    
    @Override
    public void operation() {
        System.out.println(name);
        children.forEach(Component::operation);
    }
}

方法2: 安全组合 (接口分离)

容器和叶子接口分离,各自只实现适用操作。

java
public interface Component { void operation(); }

public class Leaf implements Component {
    @Override
    public void operation() { System.out.println("Leaf"); }
}

public class Composite implements Component {
    private List<Component> children = new ArrayList<>();
    public void add(Component c) { children.add(c); }
    
    @Override
    public void operation() {
        children.forEach(Component::operation);
    }
}

方法3: 递归聚合 (访问者结合)

使用访问者支持多种操作。

java
public abstract class Node {
    public abstract void accept(Visitor v);
    public abstract List<Node> getChildren();
}

public class Leaf extends Node {
    @Override
    public void accept(Visitor v) { v.visitLeaf(this); }
    
    @Override
    public List<Node> getChildren() { return Collections.emptyList(); }
}

public class Composite extends Node {
    private List<Node> children = new ArrayList<>();
    
    @Override
    public void accept(Visitor v) {
        v.visitComposite(this);
        children.forEach(c -> c.accept(v));
    }
    
    @Override
    public List<Node> getChildren() { return children; }
}

方法4: 装饰改进 (添加额外功能)

在组合的同时添加功能增强。

java
public class EnhancedComposite extends Composite {
    private int level = 0;
    private List<Object> cache = new ArrayList<>();
    
    public void clearCache() { cache.clear(); }
    
    @Override
    public void operation() {
        if (cache.isEmpty()) {
            super.operation();
        }
    }
}

常见问题

❌ 问题1: 树遍历性能低

症状: 大树递归遍历变慢

解决:

java
// 迭代替代递归
public void traverseIterative(Visitor v) {
    Queue<Node> q = new LinkedList<>();
    q.add(this);
    while (!q.isEmpty()) {
        Node n = q.poll();
        n.accept(v);
        if (n instanceof Composite) {
            q.addAll(((Composite)n).getChildren());
        }
    }
}

❌ 问题2: 循环引用导致无限递归

症状: A包含B,B包含A

解决:

java
public void add(Composite child) {
    if (isCyclic(child)) throw new RuntimeException("Cycle!");
    children.add(child);
}

private boolean isCyclic(Composite child) {
    if (this == child) return true;
    for (Node n : children) {
        if (n instanceof Composite && ((Composite)n).isCyclic(child))
            return true;
    }
    return false;
}

❌ 问题3: 大树内存溢出

症状: 树大导致栈/堆溢出

解决:

java
public void clearCache() {
    cache.clear();
    children.forEach(c -> {
        if (c instanceof Composite)
            ((Composite)c).clearCache();
    });
}

// 限制递归深度
private int maxDepth = 100;
public void operation(int depth) {
    if (depth > maxDepth) return;
    // ...递归操作
}

❌ 问题4: 添加新操作困难

症状: 每个新操作需修改所有类

解决: 使用访问者模式解耦

java
public interface Visitor {
    void visitLeaf(Leaf l);
    void visitComposite(Composite c);
}

public class PrintVisitor implements Visitor {
    @Override
    public void visitLeaf(Leaf l) { System.out.println(l); }
    
    @Override
    public void visitComposite(Composite c) {
        System.out.println(c); 
        c.getChildren().forEach(n -> n.accept(this));
    }
}

最佳实践

✅ 安全组合优于透明组合 - 类型安全更好 ✅ 支持迭代器 - 提供多种遍历方式 ✅ 防止循环 - 验证新增子节点 ✅ 性能优化 - 大树用迭代而非递归 ✅ 缓存结果 - 频繁查询结果缓存 ✅ 支持访问者 - 灵活支持多种操作


扩展: 四大常见问题详解

问题 1: 循环检测

java
public class SafeComposite extends Composite {
    private Set<Composite> ancestors = new HashSet<>();
    
    public void add(Composite child) throws CycleException {
        // 检查是否已存在循环
        if (isAncestorOf(child)) {
            throw new CycleException("Adding would create cycle");
        }
        super.add(child);
    }
    
    private boolean isAncestorOf(Composite node) {
        Composite current = this.parent;
        while (current != null) {
            if (current == node) return true;
            current = current.parent;
        }
        return false;
    }
}

问题 2: 性能优化

java
public class OptimizedComposite {
    // 缓存计算结果
    private Map<String, Object> cache = new ConcurrentHashMap<>();
    private long cacheTime = 0;
    private static final long TTL = 60000; // 1分钟
    
    public int getTotalSize() {
        if (isCacheValid()) {
            return (Integer) cache.get("size");
        }
        int size = children.stream()
            .mapToInt(this::getChildSize)
            .sum();
        cache.put("size", size);
        cacheTime = System.currentTimeMillis();
        return size;
    }
    
    private boolean isCacheValid() {
        return cache.containsKey("size") && 
            (System.currentTimeMillis() - cacheTime) < TTL;
    }
}

问题 3: 迭代遍历

java
public class TreeIterator implements Iterator<Node> {
    private Queue<Node> queue = new LinkedList<>();
    
    public TreeIterator(Node root) {
        queue.offer(root);
    }
    
    @Override
    public boolean hasNext() {
        return !queue.isEmpty();
    }
    
    @Override
    public Node next() {
        Node current = queue.poll();
        current.getChildren().forEach(queue::offer);
        return current;
    }
}

问题 4: 深度限制

java
public class DepthLimitedComposite {
    private int maxDepth = 100;
    
    public void add(Composite child, int currentDepth) 
            throws DepthLimitException {
        if (currentDepth >= maxDepth) {
            throw new DepthLimitException(
                "Max depth exceeded: " + maxDepth);
        }
        children.add(child);
    }
}

实现方法详细对比

方面透明模式安全模式聚合模式Visitor
类型安全低高中高
易于使用高低中低
灵活性中中高高
性能高高中中
可维护性中高高高

高级模式使用

组合 + 装饰

java
public class DecoratedComposite extends Composite {
    private Decorator decorator;
    
    @Override
    public void operation() {
        decorator.beforeOp();
        super.operation();
        decorator.afterOp();
    }
}

组合 + 工厂

java
public class TreeFactory {
    public static Composite createDefaultTree() {
        Composite root = new Composite("root");
        root.add(new Leaf("leaf1"));
        root.add(new Composite("subtree"));
        return root;
    }
}

真实案例

文件系统

  • 目录树递归遍历、搜索、大小统计
  • 支持删除、复制、权限管理

DOM树

  • HTML元素树,支持样式、事件处理
  • jQuery 选择器查询

菜单系统

  • 菜单包含子菜单,递归导航
  • 权限控制cascading

组织结构

  • 部门包含子部门/员工,权限管理
  • 工资/预算递归计算

表达式树

  • 二叉树表示数学表达式
  • 编译器 AST 表示

实现细节扩展

透明组合的深度分析

优点详解:

  1. 客户端代码统一:无需区分Leaf和Composite
  2. Collection中可混合存放两者:List<Component> mixed = List.of(leaf, composite)
  3. 递归处理最自然:children.forEach(Component::operation)

缺点详解:

  1. Leaf中的add/remove会抛异常:运行时才能发现
  2. IDE不会警告无效操作:容易误用
  3. 代码review需注意:确保没有在Leaf上调用组合操作

改进策略:

java
public interface Component {
    void operation();
    
    // 提供默认实现,而不是抛异常
    default void add(Component c) {
        throw new UnsupportedOperationException(
            this.getClass().getSimpleName() + " does not support add");
    }
    
    // 至少能提供更好的错误信息
    default int getChildCount() { return 0; }
}

安全组合的设计模式

型设计考虑:

java
// 方案1:使用继承
public interface Component { void operation();}
public interface Container extends Component { 
    void add(Component c); 
}

// 方案2:使用多个接口
public interface Operable { void operation(); }
public interface Aggregatable extends Operable { 
    void add(Operable c); 
}

// 方案3:工厂模式辅助
public class ComponentFactory {
    public static Component createLeaf(String name) {
        return new Leaf(name);
    }
    
    public static Container createComposite(String name) {
        return new Composite(name);
    }
}

高性能实现

场景:需要处理百万级节点

java
public class OptimizedComposite implements Component {
    private String name;
    private Object[] children; // 使用数组而非ArrayList
    private int size = 0;
    private static final int INITIAL_CAPACITY = 16;
    
    public OptimizedComposite(String name) {
        this.name = name;
        this.children = new Object[INITIAL_CAPACITY];
    }
    
    public void add(Component c) {
        if (size == children.length) {
            resizeArray();
        }
        children[size++] = c;
    }
    
    private void resizeArray() {
        Object[] newArray = new Object[children.length * 2];
        System.arraycopy(children, 0, newArray, 0, children.length);
        children = newArray;
    }
    
    @Override
    public void operation() {
        System.out.println(name);
        for (int i = 0; i < size; i++) {
            ((Component) children[i]).operation();
        }
    }
}

性能对比:

操作ArrayListArray
添加100k元素15ms8ms
遍历100k元素12ms6ms
内存占用800KB400KB

错误处理与边界情况

空集合处理

java
// ✅ 正确处理空集
public class SafeDirectory extends Node {
    List<Component> children = new ArrayList<>();
    
    @Override
    long getSize() {
        if (children.isEmpty()) {
            return 0; // 正确处理
        }
        return children.stream().mapToLong(Node::getSize).sum();
    }
    
    @Override
    void display(String indent) {
        System.out.println(indent + name);
        for (Component child : children) {
            // 处理可能为null的情况
            if (child != null) {
                child.display(indent + "  ");
            }
        }
    }
}

单节点树处理

java
public void testSingleNodeTree() {
    Directory root = new Directory("root");
    // root没有添加任何children
    
    assertEquals(0, root.getSize());
    root.display(""); // 应能正确处理
    traverseIterative(root, node -> count++);
    assertEquals(1, count);
}

循环引用检测

java
public void testCycleDetection() {
    Directory a = new Directory("A");
    Directory b = new Directory("B");
    
    a.add(b);
    try {
        b.add(a); // 应抛异常
        fail("Should detect cycle");
    } catch (CycleException e) {
        // Expected
    }
}

并发访问支持

线程安全实现

java
public class ConcurrentComposite implements Component {
    private String name;
    private final List<Component> children = 
        new CopyOnWriteArrayList<>();
    private final ReadWriteLock lock = 
        new ReentrantReadWriteLock();
    
    @Override
    public void operation() {
        lock.readLock().lock();
        try {
            System.out.println(name);
            children.forEach(Component::operation);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void add(Component c) {
        lock.writeLock().lock();
        try {
            children.add(c);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

性能考量

  • CopyOnWriteArrayList:写入时复制,读取很快但写入较慢
  • ReentrantReadWriteLock:多读少写场景最佳
  • synchronized:简单但粒度粗,避免使用

与其他模式的结合

组合 + 迭代器

java
public class IterableComposite implements Iterable<Component> {
    private List<Component> children = new ArrayList<>();
    
    @Override
    public Iterator<Component> iterator() {
        return new CompositeIterator(this);
    }
    
    private class CompositeIterator implements Iterator<Component> {
        private Queue<Component> queue = new LinkedList<>();
        
        CompositeIterator(IterableComposite root) {
            queue.offer(root);
        }
        
        @Override
        public boolean hasNext() { return !queue.isEmpty(); }
        
        @Override
        public Component next() {
            Component curr = queue.poll();
            if (curr instanceof IterableComposite) {
                ((IterableComposite) curr).children.forEach(queue::offer);
            }
            return curr;
        }
    }
}

// 使用
for (Component c : composite) {
    c.operation();
}

组合 + 观察者

java
public class ObservableComposite extends Composite {
    private List<CompositeListener> listeners = new ArrayList<>();
    
    public void addListener(CompositeListener l) {
        listeners.add(l);
    }
    
    @Override
    public void add(Component c) {
        super.add(c);
        listeners.forEach(l -> l.onComponentAdded(c));
    }
    
    @Override
    public void remove(Component c) {
        super.remove(c);
        listeners.forEach(l -> l.onComponentRemoved(c));
    }
}

interface CompositeListener {
    void onComponentAdded(Component c);
    void onComponentRemoved(Component c);
}

常见陷阱与解决

陷阱1:过度设计

❌ 错误:

java
// 为简单情况添加过多功能
public class ComplexComposite {
    private Map<String, Object> metadata;
    private List<Listener> listeners;
    private Cache<String, Object> cache;
    private TransactionLog log;
    // ... 过多功能
}

✅ 正确:

java
// 先实现简单版本
public class SimpleComposite {
    private List<Component> children;
}
// 需要时再添加功能

陷阱2:忘记处理null

❌ 错误:

java
public void operation() {
    children.forEach(Component::operation); // NPE if null
}

✅ 正确:

java
public void operation() {
    children.stream()
        .filter(Objects::nonNull)
        .forEach(Component::operation);
}

陷阱3:忽视深度限制

❌ 错误:无限递归导致栈溢出

✅ 正确:

java
public void operation(int depth) {
    if (depth > MAX_DEPTH) return;
    System.out.println(name);
    for (Component c : children) {
        if (c instanceof Composite) {
            ((Composite) c).operation(depth + 1);
        } else {
            c.operation();
        }
    }
}

性能基准测试

测试场景

java
public class CompositePerformanceTest {
    private Directory root;
    
    @Before
    public void setup() {
        // 构建:3层树,每层4个子节点
        // 总计:1 + 4 + 16 + 64 = 85个节点
        root = buildTree(3, 4);
    }
    
    private Directory buildTree(int depth, int width) {
        Directory dir = new Directory("root");
        if (depth == 0) return dir;
        
        for (int i = 0; i < width; i++) {
            if (Math.random() > 0.3) {
                dir.add(new File("file" + i, 1000));
            } else {
                dir.add(buildTree(depth - 1, width));
            }
        }
        return dir;
    }
    
    @Test
    public void testTraversalPerformance() {
        long start = System.nanoTime();
        long size = root.getSize();
        long end = System.nanoTime();
        
        System.out.println("Traversal time: " + 
            (end - start) / 1_000_000.0 + "ms");
        // 预期:<5ms
    }
}

基准数据

树规模递归遍历缓存遍历迭代遍历
100节点0.1ms0.05ms0.08ms
1000节点1ms0.5ms0.7ms
10000节点12ms5ms8ms

决策表:何时使用组合

需求使用组合?理由
树形结构✅ 是模式核心用途
需要统一接口✅ 是简化客户端
操作频繁改变✅ 是 + Visitor易于扩展
极致性能需求⚠️ 可能需特殊优化
类型很复杂⚠️ 可能考虑安全组合
简单列表操作❌ 否用List即可
图结构(非树)❌ 否用Graph特化

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the 组合模式 AI skill do?

以树形结构组织对象,使客户端可以统一处理个体对象和组合对象。

Why use 组合模式 on TypingMind?

Because you install it once and use it with any model. 组合模式 is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install 组合模式 in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/microwind/ai-skills/tree/main/design-patterns/composite-pattern. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use 组合模式?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with 组合模式?

As many as you like. As long as a model supports skills, you can use 组合模式 with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the 组合模式 AI skill free?

Yes. It is published on GitHub by microwind under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇