Как бы вы изменили этот условный полиморфизм?

Я только что закончил смотреть видео с чистым кодом Google на YouTube (см. Ссылку , первая статья) об удалении 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заявлений?

11.12.2008 03:42:40
Поскольку вы первоначально сказали «использовать полиморфизм», а затем сказали «о нет, мне не нужен полиморфизм» - и я уже потратил немало времени, потраченного на ответ, на вопрос, который вас не волнует. Теперь, когда «использовать полиморфизм» вернулся, я убрал голос «против» и восстановил ответ
tvanfosson 11.12.2008 04:45:50
Извините за это, я не хотел тратить время зря, если бы в один из этих дней мне все еще было интересно посмотреть, как люди это сделают. Опять очень жаль.
Nathan W 11.12.2008 04:49:29
@ [Nathan W]: полиморфизм не требуется, инкапсуляция есть; подробный ответ ниже
Steven A. Lowe 11.12.2008 20:15:52
9 ОТВЕТОВ
РЕШЕНИЕ

Просто предостережение здесь после просмотра некоторых из этих (технически правильных) ответов, просто избавление от оператора If не должно быть вашей единственной целью, цель должна состоять в том, чтобы сделать ваш код расширяемым, обслуживаемым и простым, если это означает избавление от утверждение if, отлично, но оно не должно быть самоцелью.

В приведенном вами примере кода, не зная больше о вашем приложении и предполагая, что вы не собираетесь продлевать длительное прошедшее тестирование для нулевого значения, я думаю, что If (или, возможно, даже троичный) является более приемлемым решением для совершенно откровенно

8
11.12.2008 04:08:42
Да, я пытаюсь заменить все ваши операторы if каким-то полиморфным подходом, это немного сумасшествие.
BobbyShaftoe 11.12.2008 04:11:10
Да, я выпустил сейчас, это не самая умная вещь, чтобы сделать в этом случае, но я все еще хотел бы посмотреть, как люди будут атаковать его.
Nathan W 11.12.2008 04:27:54

Я мог бы рассмотреть возможность передачи извлечения возвращаемого значения другому классу, который может быть введен во время выполнения.

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 вы хотите вызвать.

0
11.12.2008 03:54:57

На самом деле вы будете реализовывать что-то вроде паттерна стратегии. Начнем с определения суперкласса, назовем его AbstractTableInfoCommand. Этот класс может быть абстрактным, но должен указывать метод с именем runTableInfoCommand ().

Затем вы можете определить несколько подклассов, каждый из которых реализует метод runTableInfoCommand (). Ваш класс, обладающий свойством Number, будет иметь новое свойство типа AbstractTableInfoCommand (назовем его tableInfoCommand), которое будет создано для одного из конкретных подклассов AbstractTableInfoCommand.

Код будет тогда:

public int Number
    {
        get
        {

            return this.tableInfoCommand.runTableInfoCommand();
        }
    }

Таким образом, вы можете создать NullTableInfoCommand, SomeOtherTableInfoCommand и т. Д. Преимущество состоит в том, что если у вас есть какое-то новое условие для возврата tableinfocommand, то вы добавляете новый класс, а не редактируете этот код.

Сказав это, не каждая ситуация обязательно подходит для этой модели. Таким образом, он делает более расширяемый код, но если вы находитесь в ситуации, которая не требует такой расширяемости, это может быть излишним.

4
11.12.2008 03:55:59

Я предполагаю internTableNameи являю InternalTableNumberсобой какую-то ценность для одной и той же вещи. Почему бы не обернуть его внутри класса и передать экземпляр этого класса this.RunTableInfoCommandтак:

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

если вы все еще хотите использовать полиморфизм, вы можете сделать это в этом классе, перегрузив, например, a, giveInternalTableIdentifierкоторый возвращает число или имя

код здесь может выглядеть так:

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

и код для internalTableClass будет тривиальным (используя: internalAbstractTableClass и два класса, которые наследуют его, один дает имя, а другой - номер)

0
11.12.2008 04:00:49

РЕДАКТИРОВАТЬ 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 с использованием полиморфизма, а затем просто использует методы интерфейса, не заботясь о реализации. Это можно легко расширить, чтобы наследовать общую реализацию RunTableCommand (int) в абстрактном классе, который наследует IFoo и является базовым классом для FooWithNum и FooWithName.

0
11.12.2008 05:02:27

Я бы рефакторинг это так:

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

Люблю троичного оператора.

0
11.12.2008 04:03:32

Я чувствую, что ваш код в порядке. Удобочитаемый. Просто. (И я надеюсь, что это работает). Если этот блок кода повторяется n раз, необходимо удалить дублирование, применив метод Extract.

Указанный вами рефакторинг предназначен для замены повторяющихся случаев переключения ... не просто, если утверждения, как в вашем примере. Заменить условное на полиморфизм . Вспомните «простейшую вещь, которая работает», что означает минимальное количество классов и методов, необходимых для выполнения работы.

1
11.12.2008 04:39:10
Если бы каждое свойство выполняло одно и то же, если / то / еще, это все равно было бы хорошим кандидатом на полиморфизм.
tvanfosson 11.12.2008 04:40:23
Почему бы не изолировать конструкцию if в метод, который затем может вызывать каждое свойство? Строка тега рефакторинга RCWP гласит: «У вас есть условие, которое выбирает другое поведение в зависимости от типа объекта». - ИМХО .. это не относится здесь.
Gishu 11.12.2008 04:43:08
Это то, что я сделал сейчас, я не думал, когда бросил это здесь.
Nathan W 11.12.2008 04:44:37

В этом случае переход к рефакторингу, который включает полиморфизм, может быть излишним (в зависимости от того, какие другие преимущества вы можете получить от полиморфизма). В этом случае можно добавить простую перегрузку, которая содержит логику RunTableInfoCommand()вызова.

Поскольку RunTableInfoCommand(), internalTableNumberи internalTableNameвсе они, похоже, являются членами одного и того же класса, хорошим, легким рефакторингом может быть добавление перегрузки, 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
0
11.12.2008 04:49:13

Как бы я рефакторинг этих конкретных, если заявления использовать полиморфизм?

Ну ... я бы не стал. Вам не нужен полиморфизм для устранения операторов if.

Обратите внимание, что операторы if как «не плохие» сами по себе, операторы if вызваны выбором представления, где

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

Эта связь подразумевает отсутствующий класс , то есть было бы более разумно иметь класс InternalTable, которому принадлежат internalTableName и internalTablenumber, поскольку эти два значения строго связаны с правилом проверки нуля.

  • Этот новый класс может предоставлять свойство TableNumber, которое выполняет проверку internalTableNumber == null
  • и RunTableInfoCommand может принимать экземпляр InternalTable в качестве параметра (но это не обязательно, см. следующий пункт).
  • Но, что еще лучше, новый класс должен иметь фасад для метода RunTableInfoCommand (который предположительно является статическим), который выполнял целочисленное преобразование.

Если предположить, что экземпляр InternalTable назван iTable, рефакторинг кода будет выглядеть следующим образом:

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

Это инкапсулирует нулевую проверку в классе InternalTable. Изменение оригинальной подписи RunTableInfoCommand не является обязательным.

Обратите внимание, что это не «заменяет операторы if полиморфизмом», но устраняет операторы if из класса-потребителя посредством инкапсуляции.

0
11.12.2008 06:18:00
Вы могли бы просто сделать функцию, чтобы скрыть нулевую проверку, но сигнатура этой функции была бы подмножеством состояния объекта, что может логически подразумевать новый класс ...
Steven A. Lowe 12.12.2008 03:08:11