私は最近、データクラスAddress
がどこかで定義され、次に別の場所で定義されているコードベースを見ました。
fun Address.toAnschrift() = let { address ->
Anschrift().apply {
// mapping code here...
}
}
このメソッドをアドレスに直接指定しないと混乱を招きました。拡張メソッドを使用するときに確立されたパターンまたはベストプラクティスはありますか?それとも、その本はまだ書かれている必要がありますか?
この例のKotlin構文にも関わらず、C#またはそれらの機能を備えた他の言語にも適用されるので、一般的なベストプラクティスに興味があることに注意してください。
拡張メソッドは、他の方法では実行できないものを提供しません。それらは、実際の技術的な利点を提供することなく、開発をより良くするための構文上の砂糖です。
拡張メソッドは比較的新しいものです。標準と慣習は通常、世論によって決定されます(さらに、何が悪いアイデアになるかについての長期的な経験も)、誰もが同意する明確な線を引くことができるようになったとは思いません。
しかし、同僚から聞いたことに基づいて、いくつかの議論を見ることができます。
1。通常は静的メソッドを置き換えます。
拡張メソッドは、通常、クラスメソッドではなく静的メソッドであるはずのものに基づいています。それらはクラスメソッドのように見えますが、実際にはそうではありません。
拡張メソッドは、クラスメソッドの代替と見なすべきではありません。むしろ構文的にugle staticメソッドに「クラスメソッド」のルックアンドフィールを与える方法として。
2。意図した方法が、ソースライブラリではなく、消費プロジェクトに固有である場合。
ライブラリとコンシューマーの両方を開発したからといって、適切な場所にロジックを配置しても構わないとは限りません。
例えば:
PersonDto
プロジェクトからのDataLayer
クラスがあります。WebUI
プロジェクトがPersonDto
をPersonViewModel
に変換できるようにしたいと考えています。この変換方法をDataLayer
プロジェクトに追加することはできません(したくありません)。このメソッドはWebUI
プロジェクトをスコープとするため、そのプロジェクト内に存在する必要があります。
拡張メソッドを使用すると、DataLayer
ライブラリを実装する必要なく、プロジェクト(およびプロジェクトのコンシューマー)内でこのメソッドにグローバルにアクセスできます。
。データクラスにデータのみを含める必要があると思われる場合
たとえば、Person
クラスには、個人のプロパティが含まれています。ただし、一部のデータをフォーマットするいくつかのメソッドを使用できるようにしたいとします。
public class Person
{
//The properties...
public SecurityQuestionAnswers GetSecurityAnswers()
{
return new SecurityQuestionAnswers()
{
MothersMaidenName = this.Mother.MaidenName,
FirstPetsName = this.Pets.OrderbyDescending(x => x.Date).FirstOrDefault()?.Name
};
}
}
クラスでデータとロジックを混ぜるのが嫌いな同僚をたくさん見ました。データのフォーマットなどの単純なことにはあまり同意しませんが、上の例では、Person
がSecurityQuestionAnswers
に依存する必要があるのは不快だと思います。
このメソッドを拡張メソッドに配置すると、純粋なデータクラスを破壊することを防ぎます。
この引数はスタイルの1つであることに注意してください。これは部分クラスに似ています。そうすることには技術的なメリットはほとんどありませんが、コードを複数のファイルに分割することができます。
4。ヘルパーメソッド
ほとんどのプロジェクトでは、ヘルパークラスを作成する傾向があります。これらは通常、簡単なフォーマットのためのメソッドのコレクションを提供する静的クラスです。
例えば。私が働いていたある会社には、使用したい特定の日時形式がありました。すべての場所にフォーマット文字列を貼り付けたり、フォーマット文字列をグローバル変数にしたりする代わりに、DateTime拡張メソッドを選択しました。
public static string ToCompanyFormat(this DateTime dt)
{
return dt.ToString("...");
}
通常の静的ヘルパーメソッドよりも優れていますか?私はそう思う。構文をクリーンアップします。の代わりに
DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);
私はそれをできた:
myObj.CreatedOn.ToCompanyFormat();
これは、同じ構文の流暢バージョンに似ています。どちらか一方を持っていることによる技術的なメリットはありませんが、私はもっと好きです。
これらの議論はすべて主観的です。それらのいずれもそうでなければカバーすることができなかったケースをカバーしません。
しかし、繰り返しになりますが、これらは極端に必要なものではないというあなたの主張を受け入れることができます。
この質問とあなたの質問への答えは同じです。
ただし、特筆すべき点は次のとおりです。
私は Flater に同意します。慣習を結晶化するのに十分な時間はありませんでしたが、.NET Frameworkクラスライブラリ自体の例はあります。 LINQメソッドが.NETに追加されたとき、それらはIEnumerable
に直接追加されたのではなく、拡張メソッドとして追加されたことを考慮してください。
これから何が取れますか? LINQは
A.B().C()
の代わりにC(B(A))
)。これら3つの機能をすべて備えている場合、拡張メソッドは非常に理にかなっています。実際、メソッドのセットに機能#3と最初の2つの機能のいずれかがある場合、それらを拡張メソッドにすることを検討する必要があると私は主張します。
拡張メソッド:
null
値から呼び出された拡張メソッドが最初の引数としてnull
を受け取るだけなので、null
値をより柔軟に処理できます。拡張メソッドにはもう1つの利点があります。それでも、静的メソッドとして使用でき、値として直接高次関数に渡すことができます。したがって、MyMethod
が拡張メソッドである場合、たとえばmyList.Select(x => x.MyMethod())
ではなく、単にmyList.Select(MyMethod)
を使用できます。私はもっといいと思います。
拡張メソッドの原動力は、シールされているか別のアセンブリにあるかに関係なく、特定のタイプのすべてのクラスまたはインターフェースに必要な機能を追加できることです。この証拠はLINQコードで確認でき、すべての関数に関数を追加しますIEnumerable<T>
オブジェクト。リストでもスタックでもかまいません。コンセプトは、将来関数を証明することであり、独自のIEnumerable<T>
LINQを自動的に使用できます。
とは言っても、言語機能は十分に新しいため、いつ使用するか使用しないかについてのガイドラインはありません。ただし、以前は不可能であった多くのことが可能になります。
公平を期すために、時間だけがどれだけ遠すぎるか、またはどの時点で拡張メソッドが恩恵ではなく障害になるかを知ることができます。別の答えで述べたように、静的メソッドでは実行できない拡張メソッドには何もありません。ただし、慎重に使用すると、コードが読みやすくなります。
同じアセンブリで意図的に拡張メソッドを使用するのはどうですか?
コア機能を十分にテストおよび信頼して、絶対に必要になるまでそれをいじらないことには価値があると思います。新しいコードに依存するが、機能を提供する必要がある新しいコード新しいコードの利点のために拡張メソッドを使用して、新しいコードをサポートする機能を追加できます。これらの関数は新しいコードにのみ関係し、拡張メソッドのスコープはそれらが宣言されている名前空間に限定されます。
拡張メソッドの使用を盲目的に排除するつもりはありませんが、十分にテストされた既存のコアコードに新しい機能を本当に追加する必要があるかどうかを検討します。
拡張メソッドを使用するときに確立されたパターンまたはベストプラクティスはありますか?
拡張メソッドを使用する最も一般的な理由の1つは、インターフェースを「変更するのではなく拡張する」手段としてです。 IFoo
とIFoo2
ではなく、後者が新しいメソッドBar
を導入するのではなく、Bar
が拡張メソッドを介してIFoo
に追加されます。 。
LinqがWhere
、Select
などに拡張メソッドを使用する理由の1つは、ユーザーがこれらのメソッドの独自のバージョンを定義できるようにするためです。これらのメソッドは、Linqクエリ構文でも使用されます。
それを超えて、私が使用するアプローチは、少なくとも.NETフレームワークで一般的に見られるものに適合しているようです。
したがって、Option<T>
がある場合、HasValue
、Value
、==
などを型自体の中に入れます。しかし、モナドをT1
からT2
に変換するMap
関数のようなものとして、拡張メソッドを追加します。
public static Option<TOutput> Map<TInput, TOutput>(this Option<TInput> input,
Func<TInput, TOutput> f) => ...