メソッドのオーバーロード。使いすぎてもいいですか?
-
05-07-2019 - |
質問
異なるフィルターを使用して同じ形状のデータを返す複数のメソッドを定義する場合、より良い方法は何でしょうか?明示的なメソッド名またはオーバーロードされたメソッド?
例えば。いくつかの製品があり、データベースから取得している場合
明示的な方法:
public List<Product> GetProduct(int productId) { // return a List }
public List<Product> GetProductByCategory(Category category) { // return a List }
public List<Product> GetProductByName(string Name ) { // return a List }
オーバーロードされた方法:
public List<Product> GetProducts() { // return a List of all products }
public List<Product> GetProducts(Category category) { // return a List by Category }
public List<Product> GetProducts(string searchString ) { // return a List by search string }
問題が発生する可能性があることは承知しています 似たような署名, ただし、基本型 (string、int、char、DateTime など) の代わりにオブジェクトを渡す場合、これはあまり問題になりません。それで...するのは良い考えですか メソッドをオーバーロードする メソッドの数を減らし、わかりやすくするために、 または すべき それぞれの方法 別の方法でデータをフィルタリングする メソッド名が違う?
解決
はい、過負荷は簡単に使いすぎてしまう可能性があります。
オーバーロードが保証されるかどうかを判断する鍵は、対象者を考慮することであることがわかりました。コンパイラではなく、数週間、数か月、または数年後に登場し、コードが何であるかを理解する必要があるメンテナンスプログラマです。達成しようとしています。
GetProducts() のような単純なメソッド名は明確で理解できますが、言い表されていないことがたくさんあります。
多くの場合、GetProducts() に渡されるパラメーターに適切な名前が付けられていれば、メンテナンス担当者はオーバーロードが何を行うのかを理解することができます。ただし、それは使用時に適切な名前付け規則が必要であり、強制することはできません。強制できるのは、呼び出しているメソッドの名前です。
私が従うガイドラインは、メソッドが交換可能である場合、つまり同じことを行う場合にのみメソッドをオーバーロードすることです。そうすれば、クラスのコンシューマがどのバージョンを呼び出すかは同等であるため、気にする必要はありません。
例として、DeleteFile() メソッドのオーバーロードを喜んで使用します。
void DeleteFile(string filePath);
void DeleteFile(FileInfo file);
void DeleteFile(DirectoryInfo directory, string fileName);
ただし、例としては別の名前を使用します。
public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByName(string Name ) {...}
フルネームを使用すると、メンテナンス担当者 (おそらく私) にとってコードがより明確になります。これにより、署名の衝突による問題が回避されます。
// No collisions, even though both methods take int parameters
public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesByDepartment(int departmentId);
目的ごとにオーバーロードを導入する機会もあります。
// Examples for GetEmployees
public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesBySupervisor(Supervisor supervisor);
public IList<Employee> GetEmployeesBySupervisor(Person supervisor);
public IList<Employee> GetEmployeesByDepartment(int departmentId);
public IList<Employee> GetEmployeesByDepartment(Department department);
// Examples for GetProduct
public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductById(params int[] productId) {...}
public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByCategory(IEnumerable<Category> category) {...}
public IList<Product> GetProductByCategory(params Category[] category) {...}
コードは書かれるよりも読み取られることが多く、ソース管理への最初のチェックイン後にコードに戻らなくても、コードを作成している間、コードのその行を数十回読み取ることになります。続くコード。
最後に、使い捨てのコードを作成している場合を除き、他の人が他の言語からコードを呼び出すことを許可する必要があります。ほとんどのビジネス システムは、使用期限を大幅に過ぎても実稼働状態のままになっているようです。2016 年にクラスを使用するコードは、VB.NET、C# 6.0、F#、またはまだ発明されていないまったく新しいもので書かれることになる可能性があります。言語がオーバーロードをサポートしていない可能性があります。
他のヒント
私が知る限り、メソッドは少なくなり、名前は少なくなります。私は一般的にオーバーロードされたネーミングのメソッドシステムを好みますが、コードをコメントして文書化する限り、それが実際に大きな違いを生むとは思いません(どちらの場合でも行うべきです)。
それを使いすぎないか?まあ、はい、それは本当です。
ただし、指定した例は、メソッドのオーバーロードを使用する場合の完璧な例です。これらはすべて同じ機能を実行します。異なるタイプを渡すだけで、異なる名前を付けるのはなぜですか。
主なルールは、最も明確で最も理解しやすいことです。単に滑らかで賢いためにオーバーロードを使用しないでください、それが理にかなっているときにそれをしてください。他の開発者もこのコードに取り組んでいる可能性があります。コードを拾い上げて理解し、バグを実装せずに変更を実装できるように、できる限り簡単にしたいのです。
私はメソッドをオーバーロードするのが好きなので、インテリセンスの後半で同じメソッドを百万個もたないようにしています。そして、数十回異なる名前を付けるのではなく、単にオーバーロードさせる方が論理的に思えます。
考慮すべきことの1つは、オーバーロードされたメソッドをWCF Webサービスの操作コントラクトとして公開できないことです。したがって、これを行う必要があると思われる場合は、異なるメソッド名を使用するための引数になります。
別のメソッド名のもう1つの引数は、インテリセンスを使用するとより簡単に検出できる可能性があることです。
しかし、どちらの選択にも長所と短所があります-すべてのデザインはトレードオフです。
おそらく、プロジェクト全体の標準が必要です。個人的には、オーバーロードされたメソッドは読みやすくなっています。 IDEをサポートしている場合は、それを選択してください。
コードを使用している人の学習曲線を緩和し、メソッドの実行内容をユーザーに知らせる命名スキームを使用できるようにするためのオーバーロードのポイント。
すべてが従業員のコレクションを返す10種類のメソッドがある場合、10個の異なる名前を生成すると(特に異なる文字で始まる場合!)、ユーザーのインテリセンスドロップダウンに複数のエントリとして表示され、ドロップダウンの長さ、およびすべてが従業員コレクションを返す10個のメソッドのセットと、クラス内の他のメソッドとの区別を非表示にします...
.Netフレームワークによって、コンストラクターやインデクサーなどに既に適用されているものについて考えてください...それらはすべて同じ名前を強制されており、それらをオーバーロードすることによってのみ複数を作成できます...
それらをオーバーロードすると、それらはすべて1つとして表示され、それらの異なる署名とコメントは横に消えます。
異なるまたは無関係な機能を実行する場合、2つのメソッドをオーバーロードしないでください...
タイプごとに同じシグネチャを持つ2つのメソッドをオーバーロードする場合に存在する混乱について
のようにpublic List<Employee> GetEmployees(int supervisorId);
public List<Employee> GetEmployees(int departmentId); // Not Allowed !!
さて、署名を区別するために、問題のコア型のラッパーとして個別の型を作成できます。
public struct EmployeeId
{
private int empId;
public int EmployeeId { get { return empId; } set { empId = value; } }
public EmployeeId(int employeId) { empId = employeeId; }
}
public struct DepartmentId
{
// analogous content
}
// Now it's fine, as the parameters are defined as distinct types...
public List<Employee> GetEmployees(EmployeeId supervisorId);
public List<Employee> GetEmployees(DepartmentId departmentId);
別のオプションは、Queryオブジェクトを使用して&quot; WHERE句&quot;を作成することです。したがって、このようなメソッドは1つしかありません。
public List<Product> GetProducts(Query query)
Queryオブジェクトには、オブジェクト指向の方法で表現された条件が含まれます。 GetProductsは、「解析」によりクエリを取得します。 Queryオブジェクト。
メソッドへの引数にわずかな違いしかない場合、オーバーロードが過剰に使用されているのを見ました。例:
public List<Product> GetProduct(int productId) { // return a List }
public List<Product> GetProduct(int productId, int ownerId ) { // return a List }
public List<Product> GetProduct(int productId, int vendorId, boolean printInvoice) { // return a List }
私の小さな例では、2番目の int
引数が所有者または顧客IDのどちらであるかがすぐにわかりません。
フレームワークを一目見れば、多数の過負荷が容認された状態であることを納得させるはずです。無数のオーバーロードに直面して、使いやすさのためのオーバーロードのデザインは、Microsoft Framework Design Guidelines(Kwalina and Abrams、2006)のセクション5.1.1で直接対処されています。そのセクションの簡単なpr&#232; cisは次のとおりです。
-
DO 記述的なパラメータ名を使用して、短いオーバーロードで使用されるデフォルトを示します。
-
AVOID オーバーロードで任意に変化するパラメーター名。
-
AVOID は、オーバーロードされたメンバーのパラメーターの順序に一貫性がありません。
-
DO は、最長のオーバーロードのみを仮想化します(拡張性が必要な場合)。短いオーバーロードは、単に長いオーバーロードを呼び出す必要があります。
-
しない
ref
またはout
パラメーターを使用してメンバーをオーバーロードしないでください。 -
DO は、オプションの引数に
null
を渡すことを許可します。 -
DO は、デフォルトの引数でメンバーを定義するのではなく、メンバーのオーバーロードを使用します。
はい、それを使いすぎることはできますが、ここでは、その使用を制御下に保つのに役立つ別の概念があります...
.Net 3.5+を使用していて、複数のフィルターを適用する必要がある場合は、おそらくIQueryableとチェーンを使用することをお勧めします。つまり、
GetQuery<Type>().ApplyCategoryFilter(category).ApplyProductNameFilter(productName);
これにより、必要な場所でフィルタリングロジックを何度でも再利用できます。
public static IQueryable<T> ApplyXYZFilter(this IQueryable<T> query, string filter)
{
return query.Where(XYZ => XYZ == filter);
}
オーバーロードは必要なだけ使用できます。 ベストプラクティスの観点からも、同じ「操作」を実行しようとする場合はオーバーロードを使用することをお勧めします。 (全体的に)データについて。例えば。 getProduct()
また、Java APIが表示される場合、オーバーロードはどこにでもあります。それ以上の支持は得られません。
オーバーロードは、望ましい多態的な動作です。人間のプログラマーがメソッド名を覚えるのに役立ちます。 typeパラメーターでexplicitが冗長な場合、それは悪いです。 typeパラメーターがメソッドの実行内容を暗示していない場合、明示的に意味が始まります。
この例では、getProductByNameが明示的な意味を持つ唯一のケースです。他の文字列で製品を取得する場合があるためです。この問題は、プリミティブ型のあいまいさが原因で発生しました。 getProduct(Name n)は、場合によってはより優れたオーバーロードソリューションかもしれません。
はい、あなたはそれを酷使することができます。あなたの例では、最初と3番目はおそらく1つのアイテムを返し、2番目はいくつかのアイテムを返すように見えます。それが正しければ、1番目と3番目のGetProductと2番目のGetProductsまたはGetProductListを呼び出します
これが当てはまらず、3つすべてが複数を返す場合(productID 5を渡すように、productidに5を含むアイテムを返すか、名前にstringパラメーターを含むアイテムを返します)、次に呼び出します3つすべてのGetProductsまたはGetProductListですべてをオーバーライドします。
いずれにしても、名前は関数の機能を反映する必要があるため、製品のリストを返すときにGetProduct(単数形)を呼び出すと、適切な関数名が作成されません。 IMNSHO
私は&quot; explicit&quot;のファンです。方法:各関数に異なる名前を付ける。過去に多くの Add(...)
関数があったコードを AddRecord(const Record&amp;)
、 AddCell(const Cell&amp;)
など。
これにより、混乱、不注意によるキャスト(少なくともC ++での)、およびコンパイラの警告を回避でき、わかりやすくなります。
場合によっては、他の戦略が必要になることがあります。まだ遭遇していません。
方法について
public IList<Product> GetProducts() { /* Return all. */}
public IList<Product> GetProductBy(int productId) {...}
public IList<Product> GetProductBy(Category category) {...}
public IList<Product> GetProductBy(string Name ) {...}
など?