そこで、私は流行の罠に陥り、大量のlinqクエリを拡張メソッドに置き換え始めました。
例えば:
orders.Where(o => o.Status == ShippedStatus.Shipped).Select(o => o.ID)
になった :
orders.ShippedOrderIds
拡張メソッドはすべての短所を暗示する静的であり、私は「最も正しい」OOこの種類のリファクタリングを処理する方法は、それを「Orders」オブジェクトにラップしてプロパティを公開することだと思います代わりにこれを行う(ies)。
いくつかの質問:
リファクタリングコンテキストの簡単な説明-これらのリファクタリングはいずれも、単一のオブジェクトではなく、オブジェクトのコレクションに対してのみ機能します。
拡張メソッドは、まったく異なる方法で問題について推論する機会を提供します-- 機能的に 。関数型プログラミングは、オブジェクト指向とはまったく異なるパラダイムであり、特定の利点があります。たとえば、関数型の思考と不変変数を使用すると、ロックや同時実行メカニズムをまったく使用せずに、プログラムが即座にスレッドセーフになります。さらに、 pure functions と書くと、単体テストが簡単になり、モックやスタブはまったく必要ありません。さらに、拡張メソッドを使用すると、メソッドをインターフェイスに配置できます(クラスはまったく必要ありません)。一度実装するだけで、ベースを共有していなくても、インターフェイスを実装するクラスで動作します。クラス。
この方法でコードを適切に機能させるためのルールを次に示します。
pure となるように拡張メソッドを記述します。メソッド入力以外の状態にアクセスしたり、状態を変更したりしないでください。
拡張メソッドは、渡されたインスタンスを変更するのではなく、常に新しいインスタンスを返すように記述します。
可能な場合は一般的にそれらを記述してください。これにより、型を変換できる型セーフなメソッドチェーンが可能になります。
メソッドに依存関係を持たせる必要がある場合は、メソッドをパラメーターとして挿入します。 Where
は、呼び出し元が論理エンジンを「挿入」できるデリゲートを受け入れます(Func<T,bool>
)行が含まれるかどうかを決定します。
ちなみに、すべてのLINQメソッドはこれらのルールに準拠しています。
たとえば、「発送済み」ステータスのクラスが複数ある場合は、次のように実装できます。
interface IShippable
{
ShippedStatus ShippedStatus { get; }
int Id;
}
static public IEnumerable<T> ShippedOrderIds<T>(this IEnumerable<T> source) where T : IShippable
{
return source.Where( s => s.ShippedStatus == ShippedState.Shipped ).Select( s => s.Id );
}
これは、アップキャストまたはダウンキャストを必要とせずに、出荷可能なアイテムで機能します。純粋なので、依存関係を挿入する必要はなく、モックや分離するものはありません。また、他の拡張メソッドを作成する場合は、タイプセーフなメソッドチェーンを許可します。
これは悪いことではありません。それが悪い可能性がある唯一の方法は、あなたがオブジェクト指向のコードを書いていると思っている場合です。あなたではない。あなたは関数型コードを書いていますが、それは良いことです。
拡張メソッドは静的であり、すべての短所が暗示されています。
拡張メソッドは、最初のパラメーターとしてthis
を取り、this
の内部動作を公開しない単なる静的メソッドであるため、開発者は純粋な「成功の秘訣」を突き止めます関数。静的メソッドの主な「欠点」は、グローバル状態にアクセスするか、さらに悪い場合には変更する場合です。これは、拡張メソッドの場合とは異なります(または正しく行われていれば、少なくともそうであってはなりません)。
したがって、適切に記述された拡張メソッドを使用する唯一の本当の「欠点」は、「OO純粋主義者」を混乱させることです。
そして、私は、この種のリファクタリングを処理する「最も正しい」OO方法は、それを「Orders」オブジェクトにラップし、代わりにこれを行うプロパティを公開することだと考えています。
それはしばしば純粋主義的なアプローチをとろうとすることに関する問題です。 「純粋なOO」の観点からは「正しい」かもしれませんが、ラッパークラスの複雑さを追加し、以前に持っていたeg List<Order>
を処理するのではなく、インスタンスを作成する必要があります。 。そして、複雑さが追加された唯一の理由は、より「正しく」見えるようにすることです。
経験則として、独自のクラスの場合、オブジェクトにdo何かを行うメソッド(内部状態にアクセスまたは変更する)を使用し、何かを行うには拡張メソッドwithオブジェクトを使用します(通常、例に従って変換します)。拡張メソッドは、他のクラスを拡張する手段としてだけでなく、補助的な機能でメインクラスファイルを乱雑にすることなく、独自のクラスを拡張するための優れた方法でもあります。
2つ目の経験則として、「純粋な」要件(処理するが変更しない)に適合する関数が必要であり、副作用なしで新しい値を導出するためにパラメーターを必要とする場合、それを静的にします。 。これにより、新しいクラスのインスタンスを作成する必要がなくなり、コードが簡素化されます。構文的に、拡張メソッドとして使用することが理にかなっている場合は、それも同じようにしてください。
C#はOO言語ではありません。これは、OO機能を備えたマルチパラダイム言語です。したがって、「OO純粋主義者」の極端な見方を気にしないでください。要件に最適なアプローチを決定するとき。
拡張メソッドは、拡張されているクラス(たとえば、int
やstring
などのフレームワーククラス)にアクセスできない場合に非常に役立ちます。それらをクラスに追加することは不可能であるため、static
メソッドを記述する必要があり、拡張メソッドの方が読みやすくなっています。 Orders
クラスにアクセスできる場合は、そのクラスにメソッドまたはプロパティを追加する必要があります。
orders
オブジェクトがIEnumerable<Order>
のインスタンスである場合(Order
は独自のクラスです)、IEnumerable<Order>
のいくつかの拡張メソッドは問題ありません。適切なOrders
クラス(IEnumerable
を適切に実装できる)を作成します。これらの拡張メソッドの多くを作成する場合は、代わりに適切なOrders
クラスに追加する必要があります。私は一歩下がって、これが本当にあなたにどの程度の改善をもたらすかを本当に調べます。
このリファクタリングを続行する場合は、これらのメソッドのいずれかを組み合わせることができるかどうかを検討してください。たとえば、個別のorders.ShippedOrderIds
、orders.CancelledOrderIds
、orders.ProcessingOrderIds
などではなく、orders.GetOrderIdsByStatus(ShippedStatus status)
。