您如何重构此条件以使用多态?

时间:2008-12-11 03:42:40

标签: c# .net polymorphism

我刚看完YouTube上的Google清洁代码视频(请参阅link,第一篇文章),关于从代码中删除if语句并使用多态。

在观看视频之后,我看了一些我在观看视频之前编写的代码,并注意到我可以使用这种方法的一些地方,主要是多次实现相同类型逻辑的地方。举个例子:

我有一些像这样的代码。

public int Number
{
    get
    {
        string returnValue;
        if (this.internalTableNumber == null)
             returnValue = this.RunTableInfoCommand(internalTableName,
                                                    TableInfoEnum.TAB_INFO_NUM);
        else
             returnValue = this.RunTableInfoCommand(internalTableNumber.Value,
                                                    TableInfoEnum.TAB_INFO_NUM);
        return Convert.ToInt32(returnValue);
    }
}

RunTableInfoCommand的作用并不重要,但主要的是我有许多属性具有完全相同的if个参数,唯一改变的是TableInfoEnum。

我想知道是否有人可以帮我重构一下,以便它仍然可以做同样的事情,但没有任何if语句?

9 个答案:

答案 0 :(得分:8)

在看到其中一些(技术上正确的)响应后,这里只是一个注意事项,只是摆脱一个If语句不应该是你唯一的目标,目标应该是使你的代码可扩展,可维护和简单,如果这意味着摆脱if语句,很好,但它本身不应该是一个目标。

在您提供的代码示例中,并且在不了解您的应用程序的更多信息的情况下,假设您不会延长过去测试的空值,我认为If(或者甚至可能是三元组)更易于维护解决方案是完全坦率的。

答案 1 :(得分:4)

您实际上将实施类似策略模式的内容。首先定义一个超类,让我们称之为AbstractTableInfoCommand。这个类可能是抽象的,但必须指定一个名为runTableInfoCommand()的方法。

然后,您可以定义几个子类,每个子类都实现runTableInfoCommand()方法。你的类,具有Number属性的类,将具有一个类型为AbstractTableInfoCommand的新属性(让我们称之为tableInfoCommand),它将被实例化为AbstractTableInfoCommand的一个具体子类。

代码将是:

public int Number
    {
        get
        {

            return this.tableInfoCommand.runTableInfoCommand();
        }
    }

所以你可以创建一个NullTableInfoCommand和SomeOtherTableInfoCommand等。优点是如果你有一个新的条件来返回tableinfocommand然后你添加一个新的类而不是编辑这个代码。

话虽如此,并非所有情况都适合这种模式。所以它会产生更多可扩展的代码,但是如果你处于不需要可扩展性的情况下,那就太过分了。

答案 2 :(得分:1)

我觉得你的代码非常好。可读。简单。 (我希望它有效)。 如果此代码块重复n次,则需要通过应用Extract方法删除重复。

您指出的重构意味着替换重复出现的开关案例..如果您的示例中的语句不简单。 Replace Conditional With Polymorphism。记住“最简单的工作方式”......这意味着完成工作所需的最少数量的类和方法。

答案 3 :(得分:0)

我可能会考虑将返回值的提取传递给另一个可以在运行时注入的类。

public class Thing
{
  public IValueFetcher ValueFetcher { get; set; }

  public int Number 
  { 
    get 
    {
      return this.ValueFetcher.GetValue<int>(/* parameters to identify the value to fetch */);
    }
  }
}

这将处理大量重复代码并减少对接口的值源的依赖。

我认为在某些时候你可能会有一个if语句,因为你仍然需要决定要调用哪个版本的RunTableInfoCommand。

答案 4 :(得分:0)

我认为internTableNameInternalTableNumber是同一件事的某种价值。为什么不将它包装在类中并将该类的实例传递给this.RunTableInfoCommand,如下所示:

public int Number
        {
            get
            {
                string returnValue;
                internalTableClass myinstance(parameters);
                return Convert.ToInt32(this.RunTableInfoCommand(myinstance, TableInfoEnum.TAB_INFO_NUM));
            }
        }

如果您仍然想使用多态,那么您可以通过重载在该类中执行此操作,例如返回数字或名称的giveInternalTableIdentifier

这里的代码可能如下所示:

public int Number
        {
            get
            {
                string returnValue;
                internalTableClass myinstance(parameters);
                return Convert.ToInt32(this.RunTableInfoCommand(myinstance.giveInternalTableIdentifier, TableInfoEnum.TAB_INFO_NUM));
            }
        }

并且internalTableClass的代码将是微不足道的(使用:internalAbstractTableClass,以及两个继承自它的类,一个给出名称,另一个给出数字)

答案 5 :(得分:0)

编辑2 我如何真正解决问题。

我将使InternalTableNumber成为延迟加载的属性。如果它不可用,那么我将通过InternalTableName查找它。然后我总是只使用我的方法的InternalTableNumber属性。

 private int? internalTableNumber;
 private int InternalTableNumber
 {
     get
     {
         if (!internalTableNumber.HasValue)
         {
             internalTableNumber = GetValueFromTableName( internalTableName );
         }
         return internalTableNumber;
     }
     set
     {
         internalTableNumber = value;
     }
 }

 public int Number
 {
     get
     {
        string value = this.RunTableInfoCommand(InternalTableNumber,
                                                TableInfoEnum.TAB_INFO_NUM);
        return Convert.ToInt32( value );
     }
 }

编辑使用多态...

让我们假设您当前的类名为Foo,然后我将它重构为两个类,FooWithName和FooWithNumber。当您拥有表名时,FooWithName将是您将使用的类,而当您拥有表号时,FooWithNumber将是要使用的类。然后,我将使用Number方法编写每个类 - 实际上,我将编写一个接口IFoo,每个接口都实现,以便它们可以互换使用。

public interface IFoo
{
     int Number { get; }|

}

public class FooWithName : IFoo
{
     private string tableName;
     public FooWithName( string name )
     {
         this.tableName = name;
     }

     public int Number
     {
        get { return this.RunTableInfoCommand(this.tableName,
                                       TableInfoEnum.TAB_INFO_NUM);
     }

     ... rest of class, including RunTableInfoCommand(string,int);
}

public class FooWithNumber : IFoo
{
     private int tableNumber;
     public FooWithNumber( int number )
     {
         this.tableNumber = number;
     }

     public int Number
     {
        get { return this.RunTableInfoCommand(this.tableNumber,
                                       TableInfoEnum.TAB_INFO_NUM);
     }

     ... rest of class, including RunTableInfoCommand(int,int);
}

您可以这样使用它:

IFoo foo;

if (tableNumber.HasValue)
{
    foo = new FooWithNumber( tableNumber.Value );
}
else
{
    foo = new FooWithName( tableName );
}

int number = foo.Number;

显然,除非你现有的类中有很多if-then-else结构,否则这个解决方案实际上并没有太多改进。此解决方案使用多态创建IFoo,然后只使用接口方法而不关心实现。这可以很容易地扩展为在继承IFoo的抽象类中继承RunTableCommand(int)的通用实现,并且是FooWithNum和FooWithName的基类。

答案 6 :(得分:0)

我会这样重构:

table = this.internalTableNumber == null ? internalTableName : internalTableNumber.Value;
return Convert.ToInt32(this.RunTableInfoCommand(table, TableInfoEnum.TAB_INFO_NUM));

喜欢三元运算符。

答案 7 :(得分:0)

在这种情况下,进行涉及多态的重构可能是过度的(取决于您可能从多态性中获得的其他优势)。在这种情况下,添加一个简单的重载,该重载封装了要调用RunTableInfoCommand()的逻辑。

由于RunTableInfoCommand()internalTableNumberinternalTableName似乎都是同一个类的成员,因此一个好的,简单的重构可能是添加RunTableInfoCommand()的重载只需要TableInfoEnum值,并确定需要调用哪个其他RunTableInfoCommand()重载的逻辑:

private string RunTableInfoCommand( TableInfoEnum infoEnum)
{
    if (this.internalTableNumber == null) {
        return this.RunTableInfoCommand( internalTableName, infoEnum);
    }

    return this.RunTableInfoCommand( internalTableNumber.Value, infoEnum);
}

然后,具有相同if决策逻辑的众多呼叫站点可以折叠为:

returnValue = this.RunTableInfoCommand( TableInfoEnum.TAB_INFO_NUM);    
                                        // or whatever enum is appropriate

答案 8 :(得分:0)

我如何重构这些特定的if语句以使用多态?

好吧......我不会。您不需要使用多态来消除if语句。

注意if语句本身并不“坏”,if语句是由表示选择引起的

(internalTableNumber == null) ==> 
    internalTableName else internalTableNumber.Value

这种关联意味着缺少类,即拥有一个拥有internalTableName和internalTablenumber的InternalTable类更有意义,因为这两个值与空检查规则密切相关。

  • 这个新类可以提供执行internalTableNumber == null check
  • 的TableNumber属性
  • 并且RunTableInfoCommand可以将InternalTable实例作为参数(但它没有,请参阅下一项)。
  • 但更好的是,新类应该具有执行整数转换的RunTableInfoCommand方法(可能是静态的)。

假设InternalTable实例名为iTable,那么重构的重构代码将如下所示:

public int Number
{
    get
    {
        return iTable.RunTableInfoCommand(TableInfoEnum.TAB_INFO_NUM);
    }
}

这将在InternalTable类中封装null-check。修改原始RunTableInfoCommand签名是可选的。

请注意,“将if语句替换为多态”,但它确实通过封装消除了消费类中的if语句。