C# Composite(组合)模式:从冗余代码到统一树形结构
本文通过C#语言实例,解析如何在处理具有层次结构的对象集合时使用Composite模式。文章首先展示了原始设计中存在的冗余问题,随后演示了利用抽象类和接口将叶子节点与容器节点统一化的方法,并对比了透明型和安全型两种架构选择。旨在帮助开发者构建更易于扩展且逻辑清晰的树形数据结构。
问题的由来
在处理如文件系统、组织架构图等具有部分-整体层级关系的场景时,对象往往表现为树形结构。如果不借助设计模式,通常需要针对“普通节点”和“包含子节点的容器”编写不同的类和方法。随着层级的增加,这种写法会导致代码大量重复,且难以应对新增类型的挑战。
初步尝试:分别定义员工类型
为了理解需求,我们先设定一个简单的背景:建立一个包含老板、部门经理和基层员工的公司架构。在基础思路中,这两类人的行为存在差异——经理可以拥有下属,而基层员工不可以。
第1步:定义没有下属的基层员工
此类只需要显示自身的信息即可:
class BasicLevelEmployee
{
public string ID { get; set; }
public void ShowStatus(int indent)
{
// 根据缩进层级打印节点名称
string str = ID;
str = str.PadLeft(ID.Length + indent, '-');
Console.WriteLine(str);
}
}
第2步:定义拥有下属的经理类由于需要维护多层级关系,经理类不仅要显示自己,还要递归显示其所有直接下属。
class Manager
{
public string ID { get; set; }
// 分别维护不同类型的下属列表
public List<BasicLevelEmployee> Subordinate = new List<BasicLevelEmployee>();
public List<Manager> SubordinateManagers = new List<Manager>();
public void ShowStatus(int indent)
{
string str = ID;
str = str.PadLeft(ID.Length + indent, '-');
Console.WriteLine(str);
indent += 4;
// 必须遍历两个不同的列表
Subordinate.ForEach(s => s.ShowStatus(indent));
SubordinateManagers.ForEach(m => m.ShowStatus(indent));
}
public void AddSubordinate(BasicLevelEmployee e) { Subordinate.Add(e); }
public void AddSubordinate(Manager e) { SubordinateManagers.Add(e); }
public void RemoveSubordinate(BasicLevelEmployee e) { Subordinate.Remove(e); }
public void RemoveSubordinate(Manager e) { SubordinateManagers.Remove(e); }
}
第3步:组装公司架构并运行
在客户端调用中,我们需要一步步添加各个层级的对象:
class Program
{
static void Main(string[] args)
{
var boss = new Manager() { ID = "BOSS" };
var devManager = new Manager() { ID = "Dev Manager" };
var financeManager = new Manager() { ID = "Finance Manager" };
var devLead = new Manager() { ID = "Dev Lead" };
var qcLead = new Manager() { ID = "QC Lead" };
// 添加下属关系
boss.AddSubordinate(devManager);
boss.AddSubordinate(financeManager);
financeManager.AddSubordinate(new BasicLevelEmployee() { ID = "Purchase" });
devManager.AddSubordinate(devLead);
devManager.AddSubordinate(qcLead);
devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer1" });
devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer2" });
qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl1" });
qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl2" });
CompanyHierachy company = new CompanyHierachy() { CEO = boss };
company.ShowStatus();
}
}
虽然能够成功构建出如图所示的层级结构
,但这段代码暴露出了严重的缺陷:
- 缺乏抽象共性:基层员工和经理都有ID属性和ShowStatus方法,却被迫写成两个独立的类。
- 扩展困难:如果未来增加“实习生”或“外包顾问”,Manager类必须再次修改,增加新的List和维护方法,这违背了开闭原则。
使用组合模式进行重构
组合模式(Composite Pattern)通过一种透明的方式处理树形结构,让单个对象(叶子节点)和组合对象(容器节点)具有一致的接口。此时,我们对结构有了直观的演变认知
。
核心重构方案
我们需要提取出一个共同的父类(或抽象类/接口),将所有操作方法的并集放入其中。这样,无论当前对象是经理还是员工,外部调用者都可以统一对其进行操作。
透明型与ili安全型的抉择:对于叶子节点(基层员工)不支持的方法(如添加下属),有两种处理哲学:
- 透明型 (Transparent Approach):父类包含所有方法。叶子节点实现这些方法时抛出异常或忽略。优点是客户端无需知道具体类型,可直接对树进行操作
。 - 安全型 (Safe Approach):父类只包含叶子节点和组合节点共有交集的方法。若需操作子节点,必须进行向下转型。优点是保证了对叶子节点绝对的安全性
。
实施透明型重构(推荐)
我们选用业界更为常用的透明型,将所有的行为都抽象到基类中:
第1步:创建抽象雇员基类
abstract class Employee
{
public string ID { get; set; }
public abstract void ShowStatus(int indent);
public abstract void AddSubordinate(Employee e);
public abstract void RemoveSubordinate(Employee e);
}
第2步:重写基层员工类(叶子节点)
由于继承了基类,必须实现所有抽象方法。对于它无法支持的Add和Remove方法,我们引入NotImplementedException以提供明确的调试信号。
class BasicLevelEmployee : Employee
{
public override void ShowStatus(int indent)
{
string str = ID;
str = str.PadLeft(ID.Length + indent, '-');
Console.WriteLine(str);
}
public override void AddSubordinate(Employee e) { throw new NotImplementedException(); }
public override void RemoveSubordinate(Employee e) { throw new NotImplementedException(); }
}
第3步:重写经理类(容器节点)
得益于基类的统一抽象,Manager现在只需维护一个类型为 List<Employee> 的集合。无论是新员工、老员工还是新加入的副经理,都能无缝接入同一个列表中,彻底消除了冗余的分支判断。
class Manager : Employee
{
public override void ShowStatus(int indent)
{
string str = ID;
str = str.PadLeft(ID.Length + indent, '-');
Console.WriteLine(str);
indent += 4;
// 统一迭代所有类型的下属
Subordinate.ForEach(s => s.ShowStatus(indent));
}
// 统一的下属管理列表
public List<Employee> Subordinate = new List<Employee>();
public override void AddSubordinate(Employee e) { Subordinate.Add(e); }
public override void RemoveSubordinate(Employee e) { Subordinate.Remove(e); }
}
第4步:验证重构结果
客户端的构建逻辑保持不变,运行后得到的组织结构依然完整有效,但背后的代码已经实现了高度的解耦与统一化。
总结
通过上述重构,我们不仅简化了原有的臃肿代码,还赋予了系统更好的扩展能力。当需要向树结构中添加新类型的节点时,只要它们能正确实现 Employee 基类的契约,就不需要再去改动现有的任何类。这就是结构型设计模式中组合模式的强大之处。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













