C#中创建对象及嵌套子对象的适用设计模式咨询
你一开始猜测的工厂方法模式可以用,但不是核心——你这个场景最适配的是模板方法模式,工厂方法只需要用来做不同类型创建器的分发即可。
为什么选模板方法
你所有的创建逻辑流程骨架是完全固定的:
- 第一步:创建Extrusion拉伸体
- 第二步:对拉伸体做自定义配置、按需创建孔洞
- 第三步:创建Loop轮廓环
- 第四步:循环创建Vertices顶点
不同形状、不同Foundation构件的区别,仅仅是每个步骤传入的参数、是否需要额外创建子构件(比如Pedestal要附带创建Grout),完全是模板方法的典型适用场景:把固定的流程骨架固化在基类,差异逻辑拆到具体实现类里,从根上解决你现在单方法堆if-else、重复代码满天飞的问题。
现有代码的核心问题
先列下当前代码的硬伤,重构就是针对性解决这些问题:
- 一个方法塞了所有类型的创建逻辑,if-else分支堆了几百行,改一个类型的逻辑容易误碰其他类型的代码,加新类型必须改这个老方法,违反开闭原则
- 重复代码太多:CreateLoop、遍历DataRow创建Vertex的逻辑在每个分支里重复编写,只是参数不同
- 变量乱复用:objectiveElement在分支里反复用临时变量存储、覆盖,稍不注意就会把元素挂错父节点
- 通用逻辑和业务配置混在一起,找某类构件的专属规则要翻半天
具体重构步骤
1. 封装公共上下文
先把你CreateFoundation方法里传的一长串参数封装成一个上下文对象,避免后面方法参数列表太长:
// 所有创建流程共用的参数都放这里 public class FoundationCreateContext { public FoundationType FoundationType { get; set; } public FoundationColumnName HeightColumnName { get; set; } public DbElement HostElement { get; set; } public DataSet SourceDataSet { get; set; } public DataTable SourceDataTable { get; set; } public DataRow HeightDataRow { get; set; } public string ExtrusionName { get; set; } public string Note { get; set; } public Guid GroupId { get; set; } }
2. 定义抽象基类,固化创建流程
基类里把固定的创建流程写死,只把有差异的步骤定义成抽象方法或者虚方法,交给具体子类实现:
public abstract class FoundationCreatorBase { protected readonly FoundationCreateContext _ctx; // 基类构造传入上下文 protected FoundationCreatorBase(FoundationCreateContext ctx) => _ctx = ctx; // 模板方法:固定创建流程,不允许子类重写改流程 public void ExecuteCreate() { var extrusion = CreateExtrusion(); ConfigExtrusionAndCreateHoles(extrusion); var loop = CreateLoop(extrusion); CreateVertices(loop); CreateExtraChildComponents(); } // ----- 差异步骤:子类必须/可以重写 ----- // 创建拉伸体:每个类型参数不一样,子类必须实现 protected abstract DbElement CreateExtrusion(); // 拉伸体创建后的配置、打孔:默认空实现,子类需要就重写 protected virtual void ConfigExtrusionAndCreateHoles(DbElement extrusion) { } // 创建轮廓环:逻辑通用,直接在基类实现,子类有特殊需求再重写 protected virtual DbElement CreateLoop(DbElement extrusion) { return _ctx.HostElement.CreateLoop(extrusion, Guid.NewGuid().ToString()); } // 创建顶点:每个类型取的坐标字段不一样,子类必须实现 protected abstract void CreateVertices(DbElement loop); // 创建额外子构件(比如Pedestal需要创建Grout):默认空实现 protected virtual void CreateExtraChildComponents() { } // ----- 通用工具方法,所有子类共用,不用重复写 ----- protected double ParseToMm(string value) => StringToDouble(value) * 1000d; protected double GetBaseZ() => ParseToMm(_ctx.SourceDataTable.Rows[0]["ZPOINT"].ToString()); }
3. 每个构件类型单独写实现类
举个最简单的TieGirder实现类例子,逻辑非常清晰:
public class TieGirderFoundationCreator : FoundationCreatorBase { public TieGirderFoundationCreator(FoundationCreateContext ctx) : base(ctx) { } protected override DbElement CreateExtrusion() { double height = StringToDouble(_ctx.SourceDataTable.Rows[0]["TIEGIRDER_HEIGHT"].ToString()); string noteDim = _ctx.SourceDataSet.Tables[TableName.CONVERTER_TIEGIRDER_DATA.ToString()].Rows[0][FoundationColumnName.NOTE_DIM.ToString()].ToString(); return _ctx.HostElement.CreateExtrusion( _ctx.FoundationType, _ctx.HostElement, GetBaseZ(), height, _ctx.ExtrusionName, _ctx.GroupId, _ctx.Note, noteDim ); } protected override void CreateVertices(DbElement loop) { foreach (DataRow dr in _ctx.SourceDataTable.Rows) { _ctx.HostElement.CreateVertex( loop, Guid.NewGuid().ToString(), ParseToMm(dr["XPOINT"].ToString()), ParseToMm(dr["YPOINT"].ToString()), ParseToMm(dr["ZPOINT"].ToString()) ); } } }
像Pedestal这种逻辑复杂、需要额外创建Grout的类型,单独写PedestalFoundationCreator类,在重写CreateExtraChildComponents方法的时候写Grout的创建逻辑就行,不用和其他类型的代码混在一起。你最开始贴的Circle、Rectangle、Pyramid这类普通形状,也可以用完全一样的思路,写个ShapeCreatorBase基类,每个形状单独做实现类。
4. 加个简单工厂做类型分发
用简单工厂根据传入的FoundationType返回对应的创建器实例,替代原来的超长if-else:
public static class FoundationCreatorFactory { public static FoundationCreatorBase GetCreator(FoundationCreateContext ctx) { return ctx.FoundationType switch { FoundationType.FOOTING or FoundationType.SCREEDTHK or FoundationType.LEANCONCRETE or FoundationType.CRUSHEDSTONES => new SimplePlateFoundationCreator(ctx), FoundationType.PEDESTAL => new PedestalFoundationCreator(ctx), FoundationType.PILE => new PileFoundationCreator(ctx), FoundationType.TIEGIRDER => new TieGirderFoundationCreator(ctx), _ => throw new NotSupportedException($"不支持的基础类型:{ctx.FoundationType}") }; } }
5. 简化原入口方法
原来几百行的CreateFoundation方法,最后可以简化成几行:
private void CreateFoundation(FoundationType foundationType, FoundationColumnName heightColumnName, DbElement objectiveElement, DataSet dsConverterData, DataTable dtConverterData, DataRow item_height, string extrusionName, string s3d_note, Guid PDMS_GROUP_ID) { var context = new FoundationCreateContext { FoundationType = foundationType, HeightColumnName = heightColumnName, HostElement = objectiveElement, SourceDataSet = dsConverterData, SourceDataTable = dtConverterData, HeightDataRow = item_height, ExtrusionName = extrusionName, Note = s3d_note, GroupId = PDMS_GROUP_ID }; var creator = FoundationCreatorFactory.GetCreator(context); creator.ExecuteCreate(); }
重构后的收益
- 没有超长if-else了,每个类型的逻辑单独放一个文件,改哪个类型就找对应的类,不会碰坏其他逻辑
- 通用代码全部抽到基类复用,不用再到处抄CreateLoop、坐标转换的代码
- 流程在基类固化,不会出现某类构件漏写CreateLoop、漏加顶点的低级错误
- 加新类型的时候,只要新增一个实现类,在工厂里加个分支就行,不用动原有流程代码,符合开闭原则
- 像Pedestal里嵌套Grout创建的逻辑,可以在子实现类里复用基类的流程方法,不用重复写拉伸体、环、顶点的创建逻辑
内容的提问来源于stack exchange,提问作者seongmincho

