组合模式
组合模式
简介
组合(Composite)将对象组合成树形结构以表示"部分-整体"的层次。组合模式使客户端统一对待单个对象和组合对象。理解组合模式,有助于处理文件系统、组织架构、UI 控件树等层次结构。
组合模式的核心思想是"部分与整体的一致性" —— 客户端代码不需要区分当前处理的是单个对象(叶子节点)还是组合对象(容器节点),两者实现相同的接口,操作会自动递归传播到子节点。
这种模式在 UI 框架中最为常见:Windows Forms 和 WPF 中的控件树、HTML 的 DOM 树、Unity 的游戏对象层级等都是组合模式的实际应用。在业务系统中,文件系统、组织架构、菜单树、权限树等也是典型的组合结构。
特点
结构分析
UML 类图
+--------------------+
| IComponent | <-- 统一接口
+--------------------+
| +Operation() |
| +Add(component) |
| +Remove(component) |
| +GetChild(i) |
+--------------------+
^
|
+-------+-------+
| |
+--------+--------+ +--+---------+
| Leaf | | Composite |
+-----------------+ +------------+
| +Operation() | | -children |
+-----------------+ | +Operation()|
| +Add() |
| +Remove() |
+------------+树形结构示意
Root (Composite)
/ | \
A B C
/ \ |
D E F
|
G (Leaf)实现
文件系统组合
// 统一接口
public interface IFileSystemItem
{
string Name { get; }
long GetSize();
void Print(string indent = "");
}
// 叶子节点 — 文件
public class FileItem : IFileSystemItem
{
public string Name { get; }
private readonly long _size;
public FileItem(string name, long size) { Name = name; _size = size; }
public long GetSize() => _size;
public void Print(string indent = "") => Console.WriteLine($"{indent}[File] {Name} ({_size} bytes)");
}
// 组合节点 — 文件夹
public class FolderItem : IFileSystemItem
{
public string Name { get; }
private readonly List<IFileSystemItem> _children = new();
public FolderItem(string name) => Name = name;
public void Add(IFileSystemItem item) => _children.Add(item);
public void Remove(IFileSystemItem item) => _children.Remove(item);
public IReadOnlyList<IFileSystemItem> Children => _children.AsReadOnly();
public long GetSize() => _children.Sum(c => c.GetSize());
public void Print(string indent = "")
{
Console.WriteLine($"{indent}[Folder] {Name}/ ({GetSize()} bytes)");
foreach (var child in _children)
child.Print(indent + " ");
}
}
// 构建
var root = new FolderItem("项目");
root.Add(new FileItem("README.md", 2048));
var src = new FolderItem("src");
src.Add(new FileItem("Program.cs", 4096));
src.Add(new FileItem("App.config", 1024));
var models = new FolderItem("Models");
models.Add(new FileItem("User.cs", 2048));
models.Add(new FileItem("Order.cs", 3072));
src.Add(models);
root.Add(src);
root.Print();
Console.WriteLine($"总大小: {root.GetSize()} bytes");组织架构组合
public interface IOrgUnit
{
string Name { get; }
decimal GetBudget();
int GetHeadCount();
void Accept(IOrgVisitor visitor);
}
// 访问者 — 统计
public interface IOrgVisitor
{
void Visit(Employee emp);
void Visit(Department dept);
}
public class Employee : IOrgUnit
{
public string Name { get; }
public string Role { get; }
public decimal Salary { get; }
public Employee(string name, string role, decimal salary)
{
Name = name; Role = role; Salary = salary;
}
public decimal GetBudget() => Salary;
public int GetHeadCount() => 1;
public void Accept(IOrgVisitor visitor) => visitor.Visit(this);
}
public class Department : IOrgUnit
{
public string Name { get; }
private readonly List<IOrgUnit> _members = new();
public decimal AdditionalBudget { get; }
public Department(string name, decimal additionalBudget = 0)
{
Name = name; AdditionalBudget = additionalBudget;
}
public void Add(IOrgUnit unit) => _members.Add(unit);
public decimal GetBudget() => AdditionalBudget + _members.Sum(m => m.GetBudget());
public int GetHeadCount() => _members.Sum(m => m.GetHeadCount());
public void Accept(IOrgVisitor visitor)
{
visitor.Visit(this);
foreach (var member in _members) member.Accept(visitor);
}
}
// 使用
var techDept = new Department("技术部", 50000);
techDept.Add(new Employee("张三", "架构师", 35000));
techDept.Add(new Employee("李四", "高级开发", 25000));
var devTeam = new Department("开发组", 10000);
devTeam.Add(new Employee("王五", "开发", 18000));
devTeam.Add(new Employee("赵六", "开发", 18000));
techDept.Add(devTeam);
Console.WriteLine($"技术部预算: Y{techDept.GetBudget():N0}");
Console.WriteLine($"技术部人数: {techDept.GetHeadCount()}");UI 控件树
public interface IUIComponent
{
string Id { get; }
void Render(int depth = 0);
IUIComponent? FindById(string id);
}
public class TextComponent : IUIComponent
{
public string Id { get; }
private readonly string _text;
public TextComponent(string id, string text) { Id = id; _text = text; }
public void Render(int depth = 0) => Console.WriteLine($"{new string(' ', depth * 2)}[Text:{Id}] \"{_text}\"");
public IUIComponent? FindById(string id) => Id == id ? this : null;
}
public class ContainerComponent : IUIComponent
{
public string Id { get; }
private readonly List<IUIComponent> _children = new();
public ContainerComponent(string id) => Id = id;
public void Add(IUIComponent comp) => _children.Add(comp);
public void Render(int depth = 0)
{
Console.WriteLine($"{new string(' ', depth * 2)}[Container:{Id}]");
foreach (var child in _children) child.Render(depth + 1);
}
public IUIComponent? FindById(string id)
{
if (Id == id) return this;
return _children.Select(c => c.FindById(id)).FirstOrDefault(r => r != null);
}
}
var page = new ContainerComponent("page");
var header = new ContainerComponent("header");
header.Add(new TextComponent("title", "欢迎"));
page.Add(header);
var body = new ContainerComponent("body");
body.Add(new TextComponent("content", "正文内容"));
page.Add(body);
page.Render();实战:菜单系统组合
public abstract class MenuItemBase
{
public string Title { get; }
public string Icon { get; }
public bool IsVisible { get; set; } = true;
protected MenuItemBase(string title, string icon = "")
{
Title = title; Icon = icon;
}
public abstract void Display(int indent = 0);
public abstract bool HasChildren { get; }
}
public class MenuAction : MenuItemBase
{
public Action? OnClick { get; set; }
public MenuAction(string title, string icon = "", Action? onClick = null) : base(title, icon)
{
OnClick = onClick;
}
public override void Display(int indent = 0)
{
if (!IsVisible) return;
Console.WriteLine($"{new string(' ', indent)} {Icon} {Title}");
}
public override bool HasChildren => false;
}
public class MenuGroup : MenuItemBase
{
private readonly List<MenuItemBase> _items = new();
public MenuGroup(string title, string icon = "") : base(title, icon) { }
public void Add(MenuItemBase item) => _items.Add(item);
public void Remove(MenuItemBase item) => _items.Remove(item);
public override void Display(int indent = 0)
{
if (!IsVisible) return;
Console.WriteLine($"{new string(' ', indent)}{Icon} {Title}");
foreach (var item in _items.Where(i => i.IsVisible))
item.Display(indent + 2);
}
public override bool HasChildren => _items.Count > 0;
}
// 构建菜单
var mainMenu = new MenuGroup("系统菜单");
var fileMenu = new MenuGroup("文件", "F");
fileMenu.Add(new MenuAction("新建", "N", () => Console.WriteLine(" -> 新建文件")));
fileMenu.Add(new MenuAction("打开", "O", () => Console.WriteLine(" -> 打开文件")));
fileMenu.Add(new MenuAction("保存", "S", () => Console.WriteLine(" -> 保存文件")));
var editMenu = new MenuGroup("编辑", "E");
editMenu.Add(new MenuAction("撤销", "Z"));
editMenu.Add(new MenuAction("重做", "Y"));
var helpMenu = new MenuGroup("帮助", "H");
helpMenu.Add(new MenuAction("关于", "A"));
mainMenu.Add(fileMenu);
mainMenu.Add(editMenu);
mainMenu.Add(helpMenu);
mainMenu.Display();实战:安全组合模式(类型安全)
标准组合模式的一个问题是客户端可能在叶子节点上调用 Add/Remove,导致运行时错误。安全组合模式通过将叶子操作和容器操作分离到不同接口来解决这个问题。
// 最小接口 — 所有组件都有
public interface IComponent
{
string Name { get; }
void Print(string indent = "");
}
// 仅容器有的操作
public interface IComposite : IComponent
{
void Add(IComponent child);
void Remove(IComponent child);
IComponent? GetChild(int index);
}
public class Leaf : IComponent
{
public string Name { get; }
public Leaf(string name) => Name = name;
public void Print(string indent = "") => Console.WriteLine($"{indent}- {Name}");
}
public class Composite : IComposite
{
public string Name { get; }
private readonly List<IComponent> _children = new();
public Composite(string name) => Name = name;
public void Add(IComponent child) => _children.Add(child);
public void Remove(IComponent child) => _children.Remove(child);
public IComponent? GetChild(int index) => index >= 0 && index < _children.Count ? _children[index] : null;
public void Print(string indent = "")
{
Console.WriteLine($"{indent}+ {Name}");
foreach (var child in _children) child.Print(indent + " ");
}
}实战:表达式树求值
组合模式非常适合实现解释器中的表达式树。每个操作符节点是容器,操作数是叶子节点。
// 表达式接口
public interface IExpression
{
double Evaluate();
string ToInfix();
void Accept(IExpressionVisitor visitor);
}
// 访问者接口 — 后续扩展不修改已有类
public interface IExpressionVisitor
{
void Visit(NumberExpression expr);
void Visit(AddExpression expr);
void Visit(MultiplyExpression expr);
}
// 叶子节点 — 数字
public class NumberExpression : IExpression
{
public double Value { get; }
public NumberExpression(double value) => Value = value;
public double Evaluate() => Value;
public string ToInfix() => Value.ToString();
public void Accept(IExpressionVisitor visitor) => visitor.Visit(this);
}
// 组合节点 — 加法
public class AddExpression : IExpression
{
public IExpression Left { get; }
public IExpression Right { get; }
public AddExpression(IExpression left, IExpression right)
{
Left = left; Right = right;
}
public double Evaluate() => Left.Evaluate() + Right.Evaluate();
public string ToInfix() => $"({Left.ToInfix()} + {Right.ToInfix()})";
public void Accept(IExpressionVisitor visitor)
{
visitor.Visit(this);
Left.Accept(visitor);
Right.Accept(visitor);
}
}
// 组合节点 — 乘法
public class MultiplyExpression : IExpression
{
public IExpression Left { get; }
public IExpression Right { get; }
public MultiplyExpression(IExpression left, IExpression right)
{
Left = left; Right = right;
}
public double Evaluate() => Left.Evaluate() * Right.Evaluate();
public string ToInfix() => $"({Left.ToInfix()} * {Right.ToInfix()})";
public void Accept(IExpressionVisitor visitor)
{
visitor.Visit(this);
Left.Accept(visitor);
Right.Accept(visitor);
}
}
// 使用示例:(3 + 5) * 2 = 16
var expr = new MultiplyExpression(
new AddExpression(new NumberExpression(3), new NumberExpression(5)),
new NumberExpression(2)
);
Console.WriteLine($"{expr.ToInfix()} = {expr.Evaluate()}");实战:权限树组合
在 RBAC(基于角色的访问控制)系统中,权限天然具有树形结构,适合使用组合模式。
public interface IPermission
{
string Code { get; }
string Description { get; }
bool HasPermission(string permissionCode);
IEnumerable<string> GetAllPermissions();
}
// 叶子 — 具体权限
public class Permission : IPermission
{
public string Code { get; }
public string Description { get; }
public Permission(string code, string description)
{
Code = code; Description = description;
}
public bool HasPermission(string permissionCode) => Code == permissionCode;
public IEnumerable<string> GetAllPermissions() => new[] { Code };
}
// 组合 — 权限组
public class PermissionGroup : IPermission
{
public string Code { get; }
public string Description { get; }
private readonly List<IPermission> _children = new();
public PermissionGroup(string code, string description)
{
Code = code; Description = description;
}
public void Add(IPermission permission) => _children.Add(permission);
public void Remove(IPermission permission) => _children.Remove(permission);
public bool HasPermission(string permissionCode)
{
if (Code == permissionCode) return true;
return _children.Any(c => c.HasPermission(permissionCode));
}
public IEnumerable<string> GetAllPermissions()
{
var result = new List<string> { Code };
foreach (var child in _children)
result.AddRange(child.GetAllPermissions());
return result.Distinct();
}
}
// 构建权限树
var userModule = new PermissionGroup("user", "用户管理");
userModule.Add(new Permission("user:view", "查看用户"));
userModule.Add(new Permission("user:create", "创建用户"));
userModule.Add(new Permission("user:edit", "编辑用户"));
userModule.Add(new Permission("user:delete", "删除用户"));
var systemAdmin = new PermissionGroup("admin", "系统管理");
systemAdmin.Add(userModule);
Console.WriteLine($"是否有 user:view 权限: {systemAdmin.HasPermission("user:view")}");
Console.WriteLine($"所有权限: {string.Join(", ", systemAdmin.GetAllPermissions())}");带缓存与变更通知的组合
在大型树形结构中,频繁递归计算会带来性能问题。以下实现展示了带缓存的组合节点。
public interface ICachableComponent
{
string Name { get; }
int GetValue();
event EventHandler<ValueChangedEventArgs>? ValueChanged;
}
public class ValueChangedEventArgs : EventArgs
{
public string ComponentName { get; }
public int OldValue { get; }
public int NewValue { get; }
public ValueChangedEventArgs(string name, int oldVal, int newVal)
{
ComponentName = name; OldValue = oldVal; NewValue = newVal;
}
}
public class CachableLeaf : ICachableComponent
{
public string Name { get; }
private int _value;
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public CachableLeaf(string name, int value)
{
Name = name; _value = value;
}
public int GetValue() => _value;
public void SetValue(int newValue)
{
if (_value != newValue)
{
var old = _value;
_value = newValue;
ValueChanged?.Invoke(this, new ValueChangedEventArgs(Name, old, newValue));
}
}
}
public class CachableComposite : ICachableComponent
{
public string Name { get; }
private readonly List<ICachableComponent> _children = new();
private int? _cachedValue;
private bool _isDirty = true;
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public CachableComposite(string name) => Name = name;
public void Add(ICachableComponent child)
{
_children.Add(child);
child.ValueChanged += OnChildChanged;
MarkDirty();
}
public void Remove(ICachableComponent child)
{
_children.Remove(child);
child.ValueChanged -= OnChildChanged;
MarkDirty();
}
private void OnChildChanged(object? sender, ValueChangedEventArgs e)
{
MarkDirty();
}
private void MarkDirty()
{
if (!_isDirty)
{
_isDirty = true;
_cachedValue = null;
}
}
public int GetValue()
{
if (_isDirty || !_cachedValue.HasValue)
{
var old = _cachedValue ?? 0;
_cachedValue = _children.Sum(c => c.GetValue());
_isDirty = false;
if (old != _cachedValue.Value)
{
ValueChanged?.Invoke(this, new ValueChangedEventArgs(Name, old, _cachedValue.Value));
}
}
return _cachedValue.Value;
}
}迭代器遍历组合结构
递归遍历在深度较大时可能导致栈溢出,使用迭代器模式可以安全地遍历任意深度的树。
// 深度优先迭代器(非递归实现)
public class CompositeIterator : IEnumerable<IComponent>
{
private readonly IComponent _root;
public CompositeIterator(IComponent root) => _root = root;
public IEnumerator<IComponent> GetEnumerator()
{
var stack = new Stack<IComponent>();
stack.Push(_root);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
// 如果是组合节点,将子节点逆序入栈
if (current is IComposite composite)
{
var children = composite.GetType()
.GetProperty("Children")?.GetValue(composite) as IEnumerable<IComponent>;
if (children != null)
{
foreach (var child in children.Reverse())
stack.Push(child);
}
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 广度优先迭代器
public class BreadthFirstIterator : IEnumerable<IComponent>
{
private readonly IComponent _root;
public BreadthFirstIterator(IComponent root) => _root = root;
public IEnumerator<IComponent> GetEnumerator()
{
var queue = new Queue<IComponent>();
queue.Enqueue(_root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
yield return current;
if (current is IComposite composite)
{
var children = composite.GetType()
.GetProperty("Children")?.GetValue(composite) as IEnumerable<IComponent>;
if (children != null)
{
foreach (var child in children)
queue.Enqueue(child);
}
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}序列化与反序列化组合结构
在实际项目中,通常需要将组合结构持久化到数据库或 JSON。以下是 JSON 序列化的实现思路。
// 可序列化的组合节点 DTO
public class ComponentDto
{
public string Type { get; set; } = ""; // "Leaf" 或 "Composite"
public string Name { get; set; } = "";
public long? Size { get; set; } // 叶子节点的值
public List<ComponentDto>? Children { get; set; } // 容器节点的子节点
}
// 序列化:将 IFileSystemItem 转为 DTO
public static ComponentDto ToDto(IFileSystemItem item)
{
if (item is FileItem file)
{
return new ComponentDto
{
Type = "Leaf",
Name = file.Name,
Size = file.GetSize()
};
}
else if (item is FolderItem folder)
{
return new ComponentDto
{
Type = "Composite",
Name = folder.Name,
Children = folder.Children.Select(ToDto).ToList()
};
}
throw new ArgumentException($"Unknown type: {item.GetType().Name}");
}
// 反序列化:将 DTO 转回 IFileSystemItem
public static IFileSystemItem FromDto(ComponentDto dto)
{
if (dto.Type == "Leaf")
{
return new FileItem(dto.Name, dto.Size ?? 0);
}
else if (dto.Type == "Composite")
{
var folder = new FolderItem(dto.Name);
if (dto.Children != null)
{
foreach (var child in dto.Children)
folder.Add(FromDto(child));
}
return folder;
}
throw new ArgumentException($"Unknown type: {dto.Type}");
}
// 使用 System.Text.Json
using System.Text.Json;
var json = JsonSerializer.Serialize(ToDto(root), new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
var restored = FromDto(JsonSerializer.Deserialize<ComponentDto>(json)!);
restored.Print();多线程安全组合
在并发环境中,多个线程可能同时读写组合结构,需要使用线程安全的集合。
// 线程安全的组合节点
public class ConcurrentComposite : IComposite
{
public string Name { get; }
private readonly ConcurrentBag<IComponent> _children = new();
private readonly ReaderWriterLockSlim _lock = new();
public ConcurrentComposite(string name) => Name = name;
public void Add(IComponent child)
{
_lock.EnterWriteLock();
try { _children.Add(child); }
finally { _lock.ExitWriteLock(); }
}
public void Remove(IComponent child)
{
// ConcurrentBag 不直接支持 Remove,需要使用 ConcurrentDictionary 替代
// 这里展示使用锁的策略思路
throw new NotImplementedException("建议使用 ConcurrentDictionary<string, IComponent>");
}
public IComponent? GetChild(int index)
{
_lock.EnterReadLock();
try { return _children.ElementAtOrDefault(index); }
finally { _lock.ExitReadLock(); }
}
public void Print(string indent = "")
{
_lock.EnterReadLock();
try
{
Console.WriteLine($"{indent}+ {Name}");
foreach (var child in _children)
child.Print(indent + " ");
}
finally { _lock.ExitReadLock(); }
}
}性能考量与优化策略
在使用组合模式时,需要注意以下性能问题:
- 递归深度限制:默认 .NET 的栈大小约 1MB,大约支持几千层递归调用。对于可能超深的树结构,使用上面的迭代器模式进行非递归遍历。
- 缓存策略:对于计算密集型操作(如计算总大小),使用脏标记(Dirty Flag)模式避免重复计算。
- 延迟加载:如果子节点数据来自远程服务或数据库,可以实现延迟加载的组合节点。
- 批量操作:当需要添加大量子节点时,提供 AddRange 方法避免多次触发变更通知。
// 延迟加载组合节点
public class LazyFolder : IFileSystemItem
{
public string Name { get; }
private readonly Func<IEnumerable<IFileSystemItem>> _loader;
private List<IFileSystemItem>? _children;
private bool _loaded;
public LazyFolder(string name, Func<IEnumerable<IFileSystemItem>> loader)
{
Name = name; _loader = loader;
}
private void EnsureLoaded()
{
if (!_loaded)
{
_children = _loader().ToList();
_loaded = true;
}
}
public long GetSize()
{
EnsureLoaded();
return _children?.Sum(c => c.GetSize()) ?? 0;
}
public void Print(string indent = "")
{
EnsureLoaded();
Console.WriteLine($"{indent}[LazyFolder] {Name}/");
if (_children != null)
foreach (var child in _children)
child.Print(indent + " ");
}
}常见陷阱
- 循环引用:如果不小心将一个祖先节点添加为后代节点的子节点,会形成循环引用,导致递归遍历死循环。应在 Add 方法中检查。
- 内存泄漏:事件订阅未取消导致对象无法被 GC 回收。组合节点销毁时需要取消所有子节点的事件订阅。
- 过度设计:只有两层结构的简单父子关系不需要完整组合模式,一个
List<T>足矣。
// 防止循环引用的 Add 方法
public void AddSafe(IFileSystemItem item)
{
// 检查 item 是否是自身
if (ReferenceEquals(this, item))
throw new InvalidOperationException("不能将节点添加为自身的子节点");
// 检查 item 是否是祖先节点(向上遍历父链)
var ancestor = _parent;
while (ancestor != null)
{
if (ReferenceEquals(ancestor, item))
throw new InvalidOperationException("不能将祖先节点添加为子节点,会形成循环引用");
ancestor = ancestor._parent;
}
_children.Add(item);
if (item is FolderItem folder)
folder._parent = this;
}组合模式 vs 装饰器模式 vs 责任链模式
组合模式 装饰器模式 责任链模式
+--------+ +--------+ +--------+
| 树形 | | 包装 | | 链式 |
| 结构 | | 增强 | | 传递 |
+--------+ +--------+ +--------+
| 部分 | | 单一 | | 请求 |
| 与整体 | | 对象 | | 处理 |
+--------+ +--------+ +--------+
| 递归 | | 透明 | | 某个 |
| 遍历 | | 委托 | | 处理 |
+--------+ +--------+ +--------+最佳实践
- 统一接口设计:确保叶子节点和容器节点实现相同的接口,客户端无需区分。
- 考虑安全组合:如果叶子节点不应该支持 Add/Remove,使用安全组合模式。
- 控制树的深度:过深的树结构会导致栈溢出,必要时使用迭代代替递归。
- 提供父节点引用:某些场景需要从子节点访问父节点,可以在组合类中添加 Parent 属性。
- 缓存计算结果:如果容器节点的计算(如 GetSize)涉及大量子节点,考虑缓存结果。
优点
缺点
总结
组合模式用统一接口处理树形结构中的单个对象和组合对象。叶子节点(FileItem、Employee)和容器节点(FolderItem、Department)实现相同接口,客户端递归操作无需区分。适用于文件系统、组织架构、UI 控件树等层次结构。建议在需要统一处理"部分-整体"关系时使用组合模式。
组合模式的本质价值在于:当你的数据结构天然是树形的(文件系统、组织架构、UI 控件树、菜单树),且需要对整棵树进行统一操作时,组合模式提供了一种优雅的方式来递归处理所有节点,而不需要客户端区分叶子节点和容器节点。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《组合模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《组合模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《组合模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《组合模式》最大的收益和代价分别是什么?
