Skip to content

组合模式

将对象组合成树形结构,以表示“部分-整体”的层次结构。

适合的场景

  • 想表达层级结构,如文件夹、UI控件、游戏物体等等

  • 希望统一操作单个对象和组合对象,不区分处理方式

  • 想让“整体”和“部分”都遵循相同的接口

Godot节点树/Unity场景树就是一个典型的例子!

基本结构

  1. Component(组件接口):统一接口,可以是叶子也可以是组合

  2. Leaf(叶子节点):没有子节点

  3. Composite(组合节点):包含子节点,并实现统一接口

示例

csharp
// 抽象组件
abstract class FileSystemItem
{
    public string Name { get; }

    public FileSystemItem(string name) => Name = name;
    public abstract void Display(int indent = 0);
}

// 叶子节点:文件
class FileItem : FileSystemItem
{
    public FileItem(string name) : base(name) {}
    public override void Display(int indent)
    {
        Console.WriteLine(new string(' ', indent) + Name);
    }
}

// 组合节点:文件夹
class Folder : FileSystemItem
{
    private List<FileSystemItem> _children = new();
    public Folder(string name) : base(name) {}

    public void Add(FileSystemItem item) => _children.Add(item);

    public override void Display(int indent)
    {
        Console.WriteLine(new string(' ', indent) + Name);
        foreach (var item in _children)
            item.Display(indent + 2);
    }
}

// 使用方法
var root = new Folder("Root");
root.Add(new FileItem("readme.txt"));

var sub = new Folder("Assets");
sub.Add(new FileItem("player.png"));
sub.Add(new FileItem("enemy.png"));

root.Add(sub);
root.Display();

/*
输出结果
Root
  readme.txt
  Assets
    player.png
    enemy.png
*/

GDScript示例

gdscript
class UIComponent extends Node:
    func display(indent := 0):
        print(" ".repeat(indent) + name)

class UIButton extends UIComponent:
    func display(indent := 0):
        print(" ".repeat(indent) + "Button: " + name)

class UIPanel extends UIComponent:
    func display(indent := 0):
        print(" ".repeat(indent) + "Panel: " + name)
        for child in get_children():
            if child is UIComponent:
                child.display(indent + 2)
                
# 使用
func _ready():
    var root := UIPanel.new()
    root.name = "Main"
    
    var btn1 := UIButton.new()
    btn1.name = "Start"
    
    var sub_panel := UIPanel.new()
    sub_panel.name = "Settings"
    
    var btn2 := UIButton.new()
    btn2.name = "Audio"
    
    sub_panel.add_child(btn2)
    root.add_child(btn1)
    root.add_child(sub_panel)
    
    root.display()