将大量的方法重载转换成通用方法,设计问题

3

我正在尝试重构一些代码,这些代码之前是其他人编写的,因为我发现它相当不实用。以下是一个例子:

protected void SetParameterValue(SqlParameter parameter, string parameterValue, bool isNullable)
{
     if ((null == parameterValue || parameterValue == NULL_STRING) && isNullable)
            parameter.Value = DBNull.Value;
     else   parameter.Value = parameterValue;
}

protected void SetParameterValue(SqlParameter parameter, int parameterValue, bool isNullable)
{
     if (parameterValue == NULL_INT && isNullable)
            parameter.Value = DBNull.Value;
     else   parameter.Value = parameterValue;
}

protected void SetParameterValue( SqlParameter parameter, Int64 parameterValue, bool isNullable)
{
     if (parameterValue == NULL_LONG && isNullable)
            parameter.Value = DBNull.Value;
     else   parameter.Value = parameterValue;
}

像这样的还有很多。现在我需要创建一个接受新类型的方法(该类型尚未拥有方法),并决定可能可以进行一些清理,使其更好。

我的想法是创建类似于以下内容:

protected void SetParameterValue<T>(SqlParameter parameter, T parameterValue, bool isNullable)

然而,我不知道最佳的方法是什么,我可以在这个泛型方法中封装什么,还需要在单独的方法中做些什么。这样做值得吗?或者“许多方法”方法可以行吗?从泛型方法中我会得到什么好处?谢谢!


@Alex,那有点过分了。 - msarchet
由于现在可用了 Nullable 类型,您可能考虑删除那些 NULL_INT、NULL_LONG 等常量,并改为使用 int?long? 作为数据类型。 - Heinzi
问题是 NULL_INT 等出现在很多地方,我想进行一些快速重构以使事情变得更好,而不必改变很多代码(和重新测试)。 - Daniel Perez
但说真的,当一个 int 值为 0 时,你总是设置为 NULL 吗? - H H
5个回答

3

消除开关的一种方法是使用某种字典来保存委托,这些委托确定每种可能类型的空值。尽管我认为你必须坚持使用对象。因此,您将拥有一个字典,并设置如下:

private Dictionary<Type, Func<object, bool, bool>> _nullChecks = new Dictionary<Type, Func<object, bool, bool>>();

private void SetupNullChecks(){
    _nullChecks.Add(typeof(string), (object parameterValue, bool isNullable) => { return (null == parameterValue || parameterValue.ToString() == NULL_STRING) && isNullable; });
    _nullChecks.Add(typeof(int), (object parameterValue, bool isNullable) => { return (int)parameterValue == NULL_INT && isNullable; });
    _nullChecks.Add(typeof(long), (object parameterValue, bool isNullable) => { return (long)parameterValue == NULL_LONG && isNullable; });
}

您的检查将如下所示:
public void SetParameterValue(SqlParameter parameter, object parameterValue, bool isNullable)
{
    if (_nullChecks[parameterValue.GetType()].Invoke(parameterValue, isNullable))
        parameter.Value = DBNull.Value;
    else parameter.Value = parameterValue;
}

虽然,正如其他人建议的那样,改变代码使用Nullable类型会更好。


1

你总是可以做到的

Protected void SetParameterValue(SqlParameter parameter, 
 object parameterValue, bool isNullable)....

parameter.Value 接受一个对象,因此除了每种类型的验证之外,您实际上不需要将它们分开。

您可以创建一个验证参数方法,该方法反映并提取参数的类型,并检查是否针对该类型设置了空值。 类似这样的东西

bool IsNull (object value){
   if (value is int){
     check int..
   }
}
//this is a quick and dirty example, there are more elegant ways to handle it.

这样可以压缩您的类型验证和所有重载,也不需要泛型方法。

我也在想同样的问题...如果适用的话,我可能还会尝试使用Nullable<T>或null来替换NULL_STRING、NULL_INT等。 - Kevin Wienhold
我认为传递“对象”类型并进行大量装箱/拆箱不是一个好的做法。有没有使用泛型可以实现相同功能,但不需要创建一个包含每种类型的巨大开关语句? - Daniel Perez
你仍然需要进行类型转换,因为 parameter.Value 接受一个对象。 - kemiller2002
1
条件语句可能会很难处理,因为最终您将不得不询问“这个对象是什么类型?”并在获得答案后执行某些验证规则。一切都归结为如果这是X,则执行此操作。这完全取决于您将在何处提出问题,在您的示例中,您仍在这样做,只是在方法之间分离了逻辑。 - kemiller2002

0

我认为你可以使用可空类型来代替 bool isNullable。

protected void SetParameterValue<T>(SqlParameter parameter, T parameterValue)
{
if (parameterValue == null)
  parameter.Value = DBNull.Value;
else
  parameter.Value = parameterValue;
}

可能根据这个参数列表进行相应的调用。 例如: SetParameter(param,null) SetParameter(param,5)


你没有考虑到值类型不能为null这个事实。 - Klaus Byskov Pedersen
等一下,你的意思是 SetParameter<int?>(param,null) 行不通? - Eugeniu Torica
是的,但SetParameter<int>不会。由于值类型不能为null,parameterValue == null将始终为false。 - kemiller2002
@Kevin,但在这种情况下,int参数将不等于NULL_INT(问题中的值)。 - Eugeniu Torica
@jenea,除非您的验证逻辑中包含了这一点,否则某人仍然可以执行SetParameterValue<int>... 并将一个空值验证为true。其他选项添加int值并不重要。提供的通用方法允许发生这种情况。 - kemiller2002

0

“多重签名”方法定义的方式非常好 - 这正是多态性的来源。就个人而言,我更喜欢保留这种技术,而不是像你建议的那样进行重构。

然而,我会将除一个之外的所有方法体中的重复部分替换为对“主”方法的调用,并进行参数转换:

protected void SetParameterValue(SqlParameter parameter, int parameterValue, bool isNullable)
{
  SetParameterValue(parameter, (Int64)parameterValue, isNullable);
}
protected void SetParameterValue(SqlParameter parameter, Int64 parameterValue, bool isNullable)
{
  if (parameterValue == NULL_INT && isNullable)
    parameter.Value = DBNull.Value;
  else
    parameter.Value = parameterValue;
}

当然,前提是parameterValue可以在不太费劲的情况下重新转换。


0

使用switch/if语句在最后“删除”问题很困难,必须有人来完成。 您可以选择覆盖/封装对象或类中的null,但仍然需要检查每个概念中的null。

我不知道这是否会使事情变得更好,但您可以首先通过创建方法来隔离重复,如前所述,或仅隔离null检查。下面是我做的最后一个:

protected void SetParameterValue(SqlParameter parameter,object parameterValue){
    if(IsParameterNull(parameterValue) && parameter.IsNullable){
        parameter.Value = DBNull.Value;
    }
    else{
        parameter.Value = parameterValue;
    }
}

List<NULLTYPE> nulls = new List<NULLTYPE>(){new NULLTYPE(NULL_INT), new NULLTYPE(NULL_LONG), new NULLTYPE(null)}
protected bool IsParameterNull(object parameterValue){
    if(nulls.Contains(parameterValue)) return true;
    else return false;
}

这里的任务是创建一个NULLTYPE类,封装您对空值的概念,并使用nulls.Contains(parameterValue)检查该值是否存在于列表中。 您可以进一步覆盖Contains以按自己的方式进行检查,但您必须考虑要花费多少工作量。

我忘了说的另一件事是,如果你真的想编写好的代码,就从阅读《Clean Code》开始吧 http://tinyurl.com/3xyq2ob。它不是圣经,我也不是传教士,但它会开拓你的思路,或者至少有所帮助 =D - Vitor Navarro
这似乎是一个有趣的解决方案,尽管我不确定我理解NULLTYPE类的样子。当将对象与NULLTYPE进行比较时,List.Contains()如何工作?强制转换运算符?它如何考虑检查非值类型是否实际为空等问题?你难道不会最终在NULLTYPE中使用switch语句吗? - Grant Crofton
不幸的是,在某个地方,我们最终会遇到类型和其空值含义的重构或switch语句。这就是为什么我说这是一个关于你想要投入多少工作的问题。NullType类取决于开发者的需求,它可以非常简单: - Vitor Navarro
公共NullType{ 受保护的对象nullValue; 公共NullType(对象nullValue){ 这个.nullValue = nullValue; } } 按照这个思路,List.Contains()方法只检查参数值是否“等于”NULLTYPE。是的,他正在进行操作符转换,很难(但不是不可能)避免强制转换、切换或类似操作,除非重新创建类型来告诉它们什么是空值,甚至进一步接收通用值。 - Vitor Navarro
顺便说一下,我喜欢你的解决方案,我在回答你的评论之前刚读过它。 - Vitor Navarro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接