8

コードからステートメントを削除し、代わりにポリモーフィズムを使用 することについて、 YouTube で Google クリーン コード ビデオ (リンク、最初の記事を参照)を見終わったところです。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ステートメントはありませんか?

4

9 に答える 9

8

これらの(技術的に正しい)応答のいくつかを見た後、ここで注意してください。Ifステートメントを取り除くことが唯一の目的であってはなりません。コードを拡張可能、保守可能、シンプルにすることが目的である必要があります。 if ステートメントは素晴らしいですが、それ自体が目的であってはなりません。

あなたが与えたコードサンプルでは、​​あなたのアプリについて詳しく知らずに、null 値のテストをあまり拡張しないと仮定すると、If (またはおそらく 3 値でさえも) がより保守しやすいソリューションであると思います完全に率直です。

于 2008-12-11T04:08:42.947 に答える
4

実際には、Strategy パターンのようなものを実装します。まず、スーパー クラスを定義して、AbstractTableInfoCommand と呼びます。このクラスは抽象クラスの場合がありますが、runTableInfoCommand() というメソッドを指定する必要があります。

次に、それぞれが runTableInfoCommand() メソッドを実装するいくつかのサブクラスを定義できます。Number プロパティを持つクラスは、AbstractTableInfoCommand の具体的なサブクラスの 1 つにインスタンス化されるタイプ AbstractTableInfoCommand (tableInfoCommand と呼びましょう) の新しいプロパティを持ちます。

コードは次のようになります。

public int Number
    {
        get
        {

            return this.tableInfoCommand.runTableInfoCommand();
        }
    }

したがって、NullTableInfoCommand や SomeOtherTableInfoCommand などを作成できます。利点は、tableinfocommand を返すための新しい条件がある場合、このコードを編集するのではなく、新しいクラスを追加することです。

とはいえ、すべての状況がこのパターンに適しているとは限りません。したがって、より拡張可能なコードが作成されますが、その拡張性を必要としない状況にある場合は、やり過ぎになる可能性があります。

于 2008-12-11T03:55:59.603 に答える
1

あなたのコードは完全に問題ないと思います。読みやすい。単純。(そして、うまくいくことを願っています)。このコード ブロックが n 回繰り返される場合は、Extract メソッドを適用して重複を削除する必要があります。

あなたが示すリファクタリングは、繰り返し発生するスイッチケースを置き換えることを目的としています..あなたの例のような単純なifステートメントではありません。Conditional を Polymorphism に置き換えます。「機能する最も単純なもの」を覚えておいてください..これは、仕事を成し遂げるために必要なクラスとメソッドの最小数を意味します。

于 2008-12-11T04:39:10.047 に答える
0

ポリモーフィズムを使用するために、これらの特定のifステートメントをどのようにリファクタリングしますか?

ええと...私はしません。ifステートメントを削除するためにポリモーフィズムは必要ありません。

ifステートメント自体は「悪い」ではないことに注意してください。ifステートメントは、表現の選択によって引き起こされます。

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

この関連付けは、クラスが欠落していることを意味します。つまり、internalTableNameとinternalTablenumberを所有するInternalTableクラスがある方が理にかなっています。これは、これら2つの値がnullチェックルールによって強く関連付けられているためです。

  • この新しいクラスは、internalTableNumber==nullチェックを実行するTableNumberプロパティを提供できます。
  • RunTableInfoCommandは、InternalTableインスタンスをパラメーターとして受け取ることができます(ただし、そうする必要はありません。次の項目を参照してください)。
  • しかし、さらに良いことに、新しいクラスには、整数変換を実行するRunTableInfoCommandメソッド(おそらく静的)へのファサードが必要です。

InternalTableインスタンスの名前がiTableであるとすると、リファクタリングされた対象のコードは次のようになります。

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

これにより、InternalTableクラスのnullチェックがカプセル化されます。元のRunTableInfoCommand署名の変更はオプションです。

これは「ifステートメントをポリモーフィズムに置き換える」わけではありませが、カプセル化によって消費クラスからifステートメントを削除することに注意してください。

于 2008-12-11T06:18:00.657 に答える
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 */);
    }
  }
}

これにより、反復的なコードの多くが処理され、インターフェイスへの値のソースへの依存が軽減されます。

RunTableInfoCommand のどのバージョンを呼び出すかを決定する必要があるため、ある時点で if ステートメントを使用する可能性が高いと思います。

于 2008-12-11T03:54:57.420 に答える
0

私は、同じことに対してある種の価値があると思いますinternTableNameInternalTableNumberクラス内にラップして、そのクラスのインスタンスをthis.RunTableInfoCommand次のように渡してみませんか。

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

それでもポリモーフィズムを使用したい場合は、たとえばgiveInternalTableIdentifier数値または名前を返すa をオーバーロードすることにより、そのクラスで行うことができます

ここのコードは次のようになります。

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

internalTableClass のコードは簡単です (internalAbstractTableClass と、それを継承する 2 つのクラスを使用し、1 つは名前を指定し、もう 1 つは番号を指定します)。

于 2008-12-11T04:00:49.653 に答える
0

EDIT 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 );
     }
 }

EDIT ポリモーフィズムを使用して...

現在のクラスの名前が Foo であると仮定すると、それを FooWithName と FooWithNumber の 2 つのクラスにリファクタリングします。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 を継承し、FooWithNum および FooWithName の基本クラスである抽象クラスで RunTableCommand( int ) の共通実装を継承するように簡単に拡張できます。

于 2008-12-11T04:02:35.017 に答える
0

私はそれを次のようにリファクタリングします:

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

三項演算子が大好きです。

于 2008-12-11T04:03:32.630 に答える
0

この場合、ポリモーフィズムを伴うリファクタリングを行うのはやり過ぎかもしれません (ポリモーフィズムから得られるその他の利点によって異なります)。この場合、RunTableInfoCommand()呼び出すロジックをカプセル化する単純なオーバーロードを追加するのが適切かもしれません。

RunTableInfoCommand()internalTableNumber、およびinternalTableNameはすべて同じクラスのメンバーであるように見えるため、適切で簡単なリファクタリングは、単純に値を取り、他のどのオーバーロードを呼び出す必要があるかを判断するロジックを実行する のオーバーロードを追加するRunTableInfoCommand()ことですTableInfoEnumRunTableInfoCommand()

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
于 2008-12-11T04:49:13.567 に答える