修改现有方法支持多类数据的C#最佳实践建议
问题背景
知识水平
我是C#新手,已经6年没写过面向对象代码。
上下文
我的程序从Excel读取单台电机的信息,做一些数据操作/转换,转成另一种类后写入数据库。现在想扩展现有方法,让它能处理另一类数据——这类和现有Excel类字段相似但名称不同。
核心疑问
处理多个相似但不同类的方法,最佳实践是什么?
我想到几个方案:
- 写两个分别对应每个类的方法,但字段相似名不同是核心难点。
- 保留一个方法,加多个判断分支——这看起来很差。
- 把Excel类和新类合并成一个公共类,让方法只处理这个公共类。
补充说明
两类字段相似名不同的原因:
- Excel类是从Excel读取电机信息。
- 新类是从数据库读取的,字段和数据库完全一致(名称、数量)。
- 我需要把这两个类的信息转换成第三个对应另一数据库的自定义类。
代码困惑
现在代码只能处理DistQuery_Excel,不知道怎么加入另一类的处理,涉及foreach循环遍历两种类型的数据:
var DistQuery_Excel = from motor in motor_Excel orderby motor.MotorName //按电机名称字母排序 group motor by motor.MotorDist; //按配电设备分组(字符串) var DistQuery_TEI = from motor in motor_TEI orderby motor.USER_TAG //按电机名称字母排序 group motor by motor.Distribution_Equipment; //按配电设备分组(字符串) foreach (var distGroup in DistQuery_Excel) //遍历每个配电设备组 { int cirInc = 1; //新增电路的计数器 //查询该配电设备对应的可用电路数量 DM_DistEquip dist = distLookupObj(distEquip_DM, distGroup.Key); //通过配电设备名称查找DM系统中的配电设备对象 int dist_Num = dist.ixPanel; //获取配电设备的主键 int numOfDevices = distGroup.Count(); //获取该组内的设备数量 List<DMCircuit> circuits_Per_Dist = circuits_Avail.Where(cir => cir.ixPanel == dist_Num).OrderBy(cir => cir.iCirNum).ToList(); //获取该配电设备的可用电路,按电路号排序 foreach (var motor in distGroup) { DMMotor motorEx = ex_Load(motor.getName()); //检查电机是否已存在于DM系统的设备列表中,存在则返回对应DMMotor对象 //处理已有电机 if (motorEx is not null) //如果电机已存在,更新负载信息(马力、千伏安、负载系数) { motorEx.LT_HP_1 = Convert.ToString(motor.MotorHP); //马力 motorEx.LT_CON_1 = motor.MotorKVA; //千伏安 //TODO: 如果负载类型变化,更新LT_FAC_1 //TODO: 更新电压/相位 if (circuits_Per_Dist.Any()) //如果还有可用电路 { motorEx.ixCircuit1 = circuits_Per_Dist[0].ixCircuit; //分配下一个可用电路的主键 UpdateDM_Circuit(motorEx.EQU_CALLOUT, distGroup.Key + "-" + circuits_Per_Dist[0].iCirNum, motorEx.LT_CON_1, circuits_Per_Dist[0].ixCircuit, DMpath); //更新电路信息,同步到TblCircuit表 circuits_Per_Dist.RemoveAt(0); //从可用列表中移除已分配的电路 }//end of if else //无可用电路,需要新增 { motorEx.ixCircuit1 = InsertDM_Circuit(motorEx.EQU_CALLOUT, distGroup.Key + "-" + (dist.iTaps + cirInc), motorEx.LT_CON_1, dist_Num, dist.iTaps + cirInc, DMpath); //在tblCircuit表中创建新电路条目 cirInc++; }//end of else equipList_EX.Add(motorEx); //将电机加入列表,后续会和equipList_NEW合并后同步到tblEquL表 }//end of if //处理新增电机 else //如果是新负载 { DMMotor motorNew = new DMMotor().Excel2DM(motor); //将Excel电机数据转换为DM格式,赋值DMMotor属性 if (circuits_Per_Dist.Any()) { motorNew.ixCircuit1 = circuits_Per_Dist[0].ixCircuit; //分配下一个可用电路的主键 UpdateDM_Circuit(motorNew.EQU_CALLOUT, distGroup.Key + "-" + circuits_Per_Dist[0].iCirNum, motorNew.LT_CON_1, circuits_Per_Dist[0].ixCircuit, DMpath); circuits_Per_Dist.RemoveAt(0); //从可用列表中移除已分配的电路 }//end of if else { motorNew.ixCircuit1 = InsertDM_Circuit(motorNew.EQU_CALLOUT, distGroup.Key + "-" + (dist.iTaps + cirInc), motorNew.LT_CON_1, dist_Num, dist.iTaps + cirInc, DMpath); //在tblCircuit表中创建新电路条目 cirInc++; }//end of else equipList_NEW.Add(motorNew); //将电机加入列表,后续会和equipList_EX合并后同步到tblEquL表 }//end of else }//end of foreach }//end of foreach
解决方案
针对你的场景,最适合的是接口+适配器模式,既不用修改原有类(比如Excel类和数据库读取的类可能是自动生成、不能随便改),又能让核心处理逻辑复用,避免重复代码或大量分支判断。
步骤1:定义公共接口
先定义一个接口,包含核心处理逻辑需要用到的所有属性/方法,把两个类的不同字段映射到统一的接口成员:
public interface IMotorData { string MotorName { get; } //对应Excel类的MotorName,TEI类的USER_TAG string DistributionEquipment { get; } //对应Excel类的MotorDist,TEI类的Distribution_Equipment string MotorHP { get; } //对应Excel类的MotorHP,TEI类的对应字段(比如叫HP) decimal MotorKVA { get; } //对应Excel类的MotorKVA,TEI类的对应字段(比如叫KVA) string GetName(); //统一获取电机标识的方法 }
步骤2:为现有类实现适配器
如果不能直接修改Excel类和TEI类,就写适配器类包装它们,实现上面的接口:
Excel类适配器
public class ExcelMotorAdapter : IMotorData { private readonly ExcelMotor _excelMotor; //假设你的Excel类叫ExcelMotor public ExcelMotorAdapter(ExcelMotor excelMotor) { _excelMotor = excelMotor; } public string MotorName => _excelMotor.MotorName; public string DistributionEquipment => _excelMotor.MotorDist; public string MotorHP => _excelMotor.MotorHP.ToString(); //类型不一致时在这里转换 public decimal MotorKVA => _excelMotor.MotorKVA; public string GetName() => _excelMotor.getName(); }
TEI类适配器
public class TEIMotorAdapter : IMotorData { private readonly TEIMotor _teiMotor; //假设你的TEI类叫TEIMotor public TEIMotorAdapter(TEIMotor teiMotor) { _teiMotor = teiMotor; } public string MotorName => _teiMotor.USER_TAG; public string DistributionEquipment => _teiMotor.Distribution_Equipment; public string MotorHP => _teiMotor.HP.ToString(); //映射到TEI类的HP字段 public decimal MotorKVA => _teiMotor.KVA; //映射到TEI类的KVA字段 public string GetName() => _teiMotor.GetUserTag(); //适配TEI类的方法名 }
如果可以修改原有类,直接让它们实现IMotorData接口就行,不用适配器。
步骤3:重构核心处理方法
把原来的处理逻辑改成接受IEnumerable<IMotorData>类型的参数,这样不管是Excel还是TEI的数据,都先转成IMotorData集合再传入:
//重构后的核心处理方法 public void ProcessMotorData(IEnumerable<IMotorData> motorDataList, List<DM_DistEquip> distEquip_DM, List<DMCircuit> circuits_Avail, string DMpath, List<DMMotor> equipList_EX, List<DMMotor> equipList_NEW) { var distQuery = from motor in motorDataList orderby motor.MotorName group motor by motor.DistributionEquipment; foreach (var distGroup in distQuery) { int cirInc = 1; DM_DistEquip dist = distLookupObj(distEquip_DM, distGroup.Key); int dist_Num = dist.ixPanel; List<DMCircuit> circuits_Per_Dist = circuits_Avail.Where(cir => cir.ixPanel == dist_Num).OrderBy(cir => cir.iCirNum).ToList(); foreach (var motor in distGroup) { DMMotor motorEx = ex_Load(motor.GetName()); if (motorEx is not null) { motorEx.LT_HP_1 = motor.MotorHP; motorEx.LT_CON_1 = motor.MotorKVA; //TODO: 其他更新逻辑 if (circuits_Per_Dist.Any()) { motorEx.ixCircuit1 = circuits_Per_Dist[0].ixCircuit; UpdateDM_Circuit(motorEx.EQU_CALLOUT, $"{distGroup.Key}-{circuits_Per_Dist[0].iCirNum}", motorEx.LT_CON_1, circuits_Per_Dist[0].ixCircuit, DMpath); circuits_Per_Dist.RemoveAt(0); } else { motorEx.ixCircuit1 = InsertDM_Circuit(motorEx.EQU_CALLOUT, $"{distGroup.Key}-{dist.iTaps + cirInc}", motorEx.LT_CON_1, dist_Num, dist.iTaps + cirInc, DMpath); cirInc++; } equipList_EX.Add(motorEx); } else { //通用转换方法,从IMotorData转成DMMotor DMMotor motorNew = ConvertToDMMotor(motor); if (circuits_Per_Dist.Any()) { motorNew.ixCircuit1 = circuits_Per_Dist[0].ixCircuit; UpdateDM_Circuit(motorNew.EQU_CALLOUT, $"{distGroup.Key}-{circuits_Per_Dist[0].iCirNum}", motorNew.LT_CON_1, circuits_Per_Dist[0].ixCircuit, DMpath); circuits_Per_Dist.RemoveAt(0); } else { motorNew.ixCircuit1 = InsertDM_Circuit(motorNew.EQU_CALLOUT, $"{distGroup.Key}-{dist.iTaps + cirInc}", motorNew.LT_CON_1, dist_Num, dist.iTaps + cirInc, DMpath); cirInc++; } equipList_NEW.Add(motorNew); } } } } //新增的通用转换方法 private DMMotor ConvertToDMMotor(IMotorData motorData) { return new DMMotor { LT_HP_1 = motorData.MotorHP, LT_CON_1 = motorData.MotorKVA, //其他属性按需求映射 }; }
步骤4:调用重构后的方法
分别把Excel和TEI的数据转成IMotorData集合,传入同一个方法:
//处理Excel数据 var excelMotorData = motor_Excel.Select(m => new ExcelMotorAdapter(m)); ProcessMotorData(excelMotorData, distEquip_DM, circuits_Avail, DMpath, equipList_EX, equipList_NEW); //处理TEI数据 var teiMotorData = motor_TEI.Select(m => new TEIMotorAdapter(m)); ProcessMotorData(teiMotorData, distEquip_DM, circuits_Avail, DMpath, equipList_EX, equipList_NEW);
方案优势
- 复用核心逻辑:不用写两个几乎一样的方法,避免代码重复。
- 可扩展性:以后再加新的电机数据类,只需要新增一个适配器实现
IMotorData就行,不用改核心处理代码。 - 低耦合:依赖抽象(接口)而不是具体实现,符合面向对象设计原则。
简单场景替代方案
如果场景很简单,不想写接口和适配器,也可以用匿名类型先统一字段名,再传入处理:
//统一Excel数据的字段名 var unifiedExcel = motor_Excel.Select(m => new { MotorName = m.MotorName, DistributionEquipment = m.MotorDist, MotorHP = m.MotorHP.ToString(), MotorKVA = m.MotorKVA, GetName = () => m.getName() }); //统一TEI数据的字段名 var unifiedTEI = motor_TEI.Select(m => new { MotorName = m.USER_TAG, DistributionEquipment = m.Distribution_Equipment, MotorHP = m.HP.ToString(), MotorKVA = m.KVA, GetName = () => m.GetUserTag() });
但匿名类型只能在局部使用,无法跨方法传递,适合简单的一次性处理。
内容的提问来源于stack exchange,提问作者Brandon G
相关产品推荐
相关产品推荐

