如何重构所有c# Switch语句之母?
本文关键字:Switch 语句 何重构 重构 | 更新日期: 2023-09-27 18:17:31
我正在尝试重构所有交换机之母,但我不确定该如何做。下面是现有的代码:
bool FieldSave(Claim claim, string field, string value)
{
//out vars for tryparses
decimal outDec;
int outInt;
bool outBool;
DateTime outDT;
//our return value
bool FieldWasSaved = true;
//World Greatest Switch - God help us all.
switch (field)
{
case "Loan.FhaCaseNumber":
GetLoan(claim).FhaCaseNumber = value;
break;
case "Loan.FhaInsurance":
if (bool.TryParse(value, out outBool))
{
GetLoan(claim).FhaInsurance = outBool;
FieldWasSaved = true;
}
break;
case "Loan.UnpaidPrincipalBalance":
if (decimal.TryParse(value, out outDec))
{
GetLoan(claim).UnpaidPrincipalBalance = outDec;
FieldWasSaved = true;
}
break;
case "Loan.Mortgagor_MortgagorID":
if(Int32.TryParse(value, out outInt)){
GetLoan(claim).Mortgagor_MortgagorID = outInt;
FieldWasSaved = true;
}
break;
case "Loan.SystemDefaultDate":
if (DateTime.TryParse(value, out outDT))
{
GetLoan(claim).SystemDefaultDate = outDT;
FieldWasSaved = true;
}
break;
//And so on for 5 billion more cases
}
db.SaveChanges();
return FieldWasSaved;
}
是否有一种通用的方式来做到这一点,或者这个超级开关实际上是必要的?
更多的上下文我并不是说我理解其他开发者的所有神奇之处,但基本上就是"贷款"这个字符串。"FieldName"来自一些标签在HTML输入标签上的元数据。在这个开关中,它用于将特定字段链接到实体框架数据表/属性组合。尽管这是来自强类型视图,但由于人类无法理解的原因,这种映射已经成为将整个事物粘合在一起的粘合剂。
通常当我重构时,是为了减少代码的复杂性,或者使其更容易理解。在你发布的代码中,我不得不说它似乎并不那么复杂(尽管可能有很多行,但它看起来相当重复和直接)。因此,除了代码美观之外,我不确定重构开关能给您带来多少好处。
话虽如此,我可能会尝试创建一个字典,其中键是field
,值是包含每种情况代码的委托(每个方法可能会返回一个带有FieldWasSaved
值的bool值,并且会为其他4个值提供一些out-params)。然后你的方法会使用field从字典中查找委托然后调用它
当然,我可能会让代码保持原样。字典方法对其他开发人员来说可能不那么明显,并且可能使代码不太容易理解。
Update:我也同意nightwatch的观点,最好的重构可能会涉及到没有显示的代码——也许很多代码属于其他类(也许会有一个Loan类封装了所有的Loan字段,或者类似的东西…)。
如果case语句中的名称与类中的属性匹配,我会将其全部更改为使用反射。
例如,这是我们的基本业务记录核心的精简版本,我们使用它将数据移入和移出数据库、表单、web服务等。
public static void SetFieldValue(object oRecord, string sName, object oValue)
{
PropertyInfo theProperty = null;
FieldInfo theField = null;
System.Type oType = null;
try
{
oType = oRecord.GetType();
// See if the column is a property in the record
theProperty = oType.GetProperty(sName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public, null, null, new Type[0], null);
if (theProperty == null)
{
theField = oType.GetField(sName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
if (theField != null)
{
theField.SetValue(oRecord, Global.ValueFromDB(oValue, theField.FieldType.Name));
}
}
else
{
if (theProperty.CanWrite)
{
theProperty.SetValue(oRecord, Global.ValueFromDB(oValue, theProperty.PropertyType.Name), null);
}
}
}
catch (Exception theException)
{
// Do something useful here
}
}
在上面,Global。ValueFromDB是一个大的switch语句,它将值安全地转换为指定的类型。以下是部分版本:
public static object ValueFromDB(object oValue, string sTypeName)
{
switch (sTypeName.ToLower())
{
case "string":
case "system.string":
return StrFromDB(oValue);
case "boolean":
case "system.boolean":
return BoolFromDB(oValue);
case "int16":
case "system.int16":
return IntFromDB(oValue);
case "int32":
case "system.int32":
return IntFromDB(oValue);
特定于数据类型的fromdb看起来像这样:
public static string StrFromDB(object theValue)
{
return StrFromDB(theValue, "");
}
public static string StrFromDB(object theValue, string sDefaultValue)
{
if ((theValue != DBNull.Value) && (theValue != null))
{
return theValue.ToString();
}
else
{
return sDefaultValue;
}
}
public static bool BoolFromDB(object theValue)
{
return BoolFromDB(theValue, false);
}
public static bool BoolFromDB(object theValue, bool fDefaultValue)
{
if (!(string.IsNullOrEmpty(StrFromDB(theValue))))
{
return Convert.ToBoolean(theValue);
}
else
{
return fDefaultValue;
}
}
public static int IntFromDB(object theValue)
{
return IntFromDB(theValue, 0);
}
public static int IntFromDB(object theValue, int wDefaultValue)
{
if ((theValue != DBNull.Value) && (theValue != null) && IsNumeric(theValue))
{
return Convert.ToInt32(theValue);
}
else
{
return wDefaultValue;
}
}
在短期内看起来你可能不会节省很多代码,但是一旦它实现了,你会发现它有很多很多的用途(我们当然有)。
我知道回答你自己的问题很麻烦,但下面是我的老板如何使用反射和字典解决这个问题的。讽刺的是,在我们完成"所有开关之母"的几分钟后,他就完成了他的解决方案。没有人希望看到一个下午的打字变得毫无意义,但这个解决方案要灵活得多。
public JsonResult SaveField(int claimId, string field, string value)
{
try
{
var claim = db.Claims.Where(c => c.ClaimID == claimId).SingleOrDefault();
if (claim != null)
{
if(FieldSave(claim, field, value))
return Json(new DataProcessingResult { Success = true, Message = "" });
else
return Json(new DataProcessingResult { Success = false, Message = "Save Failed - Could not parse " + field });
}
else
return Json(new DataProcessingResult { Success = false, Message = "Claim not found" });
}
catch (Exception e)
{
//TODO Make this better
return Json(new DataProcessingResult { Success = false, Message = "Save Failed" });
}
}
bool FieldSave(Claim claim, string field, string value)
{
//our return value
bool FieldWasSaved = true;
string[] path = field.Split('.');
var subObject = GetMethods[path[0]](this, claim);
var secondParams = path[1];
PropertyInfo propertyInfo = subObject.GetType().GetProperty(secondParams);
if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
FieldWasSaved = SetValue[Nullable.GetUnderlyingType(propertyInfo.PropertyType)](propertyInfo, subObject, value);
}
else
{
FieldWasSaved = SetValue[propertyInfo.PropertyType](propertyInfo, subObject, value);
}
db.SaveChanges();
return FieldWasSaved;
}
// these are used for dynamically setting the value of the field passed in to save field
// Add the object look up function here.
static Dictionary<string, Func<dynamic, dynamic, dynamic>> GetMethods = new Dictionary<string, Func<dynamic, dynamic, dynamic>>()
{
{ "Loan", new Func<dynamic, dynamic, dynamic>((x, z)=> x.GetLoan(z)) },
// and so on for the 15 or 20 model classes we have
};
// This converts the string value comming to the correct data type and
// saves the value in the object
public delegate bool ConvertString(PropertyInfo prop, dynamic dynObj, string val);
static Dictionary<Type, ConvertString> SetValue = new Dictionary<Type, ConvertString>()
{
{ typeof(String), delegate(PropertyInfo prop, dynamic dynObj, string val)
{
if(prop.PropertyType == typeof(string))
{
prop.SetValue(dynObj, val, null);
return true;
}
return false;
}
},
{ typeof(Boolean), delegate(PropertyInfo prop, dynamic dynObj, string val)
{
bool outBool = false;
if (Boolean.TryParse(val, out outBool))
{
prop.SetValue(dynObj, outBool, null);
return outBool;
}
return false;
}
},
{ typeof(decimal), delegate(PropertyInfo prop, dynamic dynObj, string val)
{
decimal outVal;
if (decimal.TryParse(val, out outVal))
{
prop.SetValue(dynObj, outVal, null);
return true;
}
return false;
}
},
{ typeof(DateTime), delegate(PropertyInfo prop, dynamic dynObj, string val)
{
DateTime outVal;
if (DateTime.TryParse(val, out outVal))
{
prop.SetValue(dynObj, outVal, null);
return true;
}
return false;
}
},
};
一种可能性是创建一个Dictionary
,将字段名作为键,并将委托作为值。比如:
delegate bool FieldSaveDelegate(Claim claim, string value);
您可以为每个字段编写单独的方法:
bool SystemDefaultDateHandler(Claim cliaim, string value)
{
// do stuff here
}
初始化:
FieldSaveDispatchTable = new Dictionary<string, FieldSaveDelegate>()
{
{ "Loan.SystemDefaultDate", SystemDefaultDateHandler },
// etc, for five billion more fields
}
调度程序,然后:
FieldSaveDelegate dlgt;
if (!FieldSaveDispatchTable.TryGetValue(fieldName, out dlgt))
{
// ERROR: no entry for that field
}
dlgt(claim, value);
这可能比switch语句更容易维护,但它仍然不是特别漂亮。好处是可以自动生成填充字典的代码。
正如前面的回答所说,您可以使用反射来查找字段名以确保它是有效的,并检查类型(也使用反射)。这将减少您编写的处理程序方法的数量:每个类型一个。
即使不使用反射,也可以通过在字典中保存类型而不是委托来减少所需方法的数量。然后,您需要进行查找以获取字段的类型,并在该类型上执行一个小的switch语句。
当然,这假定您没有对每个字段进行任何特殊的处理。如果您必须对某些字段进行特殊验证或额外处理,那么您要么需要对每个字段使用一个方法,要么需要对每个字段使用一些额外的信息。
根据您的应用程序,您可以将Claim重新定义为动态对象:
class Claim : DynamicObject
{
... // Current definition
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
var property = typeof(Loan).GetProperty(binder.Name);
object param = null;
// Find some way to parse the string into a value of appropriate type:
// (This will of course need to be improved to handle more types)
if (property.PropertyType == typeof(Int32) )
{
param = Int32.Parse(value.ToString());
}
// Set property in the corresponding Loan object
property.SetValue(GetLoan(this), param, null);
db.Save();
}
catch
{
return base.TrySetMember(binder, value);
}
return true;
}
}
如果可能的话,您应该能够使用以下语法通过Claim对象间接地处理相应的Loan对象:
dynamic claim = new Claim();
// Set GetLoan(claim).Mortgagor_MortgagorID to 1723 and call db.Save():
claim.Mortgagor_MortgagorID = "1723";
对于失败的情况,当输入字符串不能被解析时,您将不幸地得到一个RuntimeBinderException而不是一个很好的函数返回值,因此您需要考虑这是否适合您的情况。