19

私はこのデザインの質問に対するきちんとした答えを探していましたが、成功しませんでした。「.NETFrameworkの設計ガイドライン」にも「C#プログラミングガイドライン」にもヘルプが見つかりませんでした。基本的に、パターンをAPIとして公開して、ユーザーが次のようにアルゴリズムを定義してフレームワークに統合できるようにする必要があります。

1)

// This what I provide
public abstract class AbstractDoSomething{
   public abstract SomeThing DoSomething();
}

ユーザーはこの抽象クラスを実装する必要があり、DoSomethingメソッドを実装する必要があります(フレームワーク内から呼び出して使用できます)

2)

これは、デリゲートを使用することでも達成できることがわかりました。

public sealed class DoSomething{
   public String Id;
   Func<SomeThing> DoSomething;
}

この場合、ユーザーは次のDoSomething方法でのみクラスを使用できます。

DoSomething do = new DoSomething()
{
  Id="ThisIsMyID",
  DoSomething = (() => new Something())
}

質問

これらの2つのオプションのどちらが、APIとして公開するのが簡単で、使いやすく、最も重要なこととして理解できるのに最適ですか?

編集

1の場合:登録はこのように行われます(MyDoSomething拡張すると仮定しますAbstractDoSomething

MyFramework.AddDoSomething("DoSomethingIdentifier", new MyDoSomething());

2の場合:登録は次のように行われます。

MyFramework.AddDoSomething(new DoSomething());
4

5 に答える 5

16

これら 2 つのオプションのうち、簡単で使いやすく、最も重要な API として公開するのに最適なものはどれですか?

1 つ目は、OOP に関してはより「伝統的」であり、多くの開発者にとってより理解しやすいものです。また、ユーザーがオブジェクトの有効期間を管理できるようにするという利点もあります (つまり、クラスIDisposableにシャットダウン時にインスタンスを実装して破棄させることができます)。また、将来のバージョンで次のように簡単に拡張できるという利点もあります。基本クラスに仮想メンバーを追加しても API が壊れないため、下位互換性が失われることはありません。最後に、MEF のようなものを使用してこれを自動的に構成したい場合は、使用が簡単になります。これにより、ユーザーの観点から「登録」のプロセスを簡素化/削除できます (サブクラスを作成し、それをフォルダを作成し、自動的に検出/使用します)。

2 つ目は、より機能的なアプローチであり、多くの点でより単純です。これにより、ユーザーは、新しい型を作成する代わりにクロージャーを使用して必要な呼び出しをラムダでラップするだけでよいため、既存のコードにほとんど変更を加えずに API を実装できます。

そうは言っても、デリゲートを使用するアプローチを取る場合は、ユーザーにクラスを作成させることさえしません。次のようなメソッドを使用するだけです。

MyFramework.AddOperation("ThisIsMyID", () => DoFoo());

これにより、私の意見では、操作をシステムに直接追加していることが少し明確になります。また、パブリック API ( DoSomething) で別の型を使用する必要が完全になくなり、API が単純化されます。

于 2012-06-11T16:19:52.110 に答える
7

次の場合は、抽象クラス/インターフェースを使用します。

  • DoSomething が必要です
  • DoSomething は通常、非常に大きくなります (そのため、DoSomething の実装は、いくつかのプライベート/保護されたメソッドに分割できます)。

次の場合は、代表者と一緒に行きます。

  • DoSomething をイベントとして扱うことができる (OnDoingSomething)
  • DoSomething はオプションです (そのため、デフォルトでノーオペレーション デリゲートになります)。
于 2012-06-11T16:22:16.267 に答える
2

個人的には、私が手にした場合、私は常にデリゲート モデルを使用します。高階関数のシンプルさと優雅さが大好きです。ただし、モデルを実装するときは、メモリ リークに注意してください。サブスクライブされたイベントは、.Net でのメモリ リークの最も一般的な原因の 1 つです。つまり、いくつかのイベントが公開されているオブジェクトがある場合、イベントが強い参照を作成するため、すべてのイベントがサブスクライブ解除されるまで元のオブジェクトが破棄されることはありません。

于 2012-06-11T17:57:06.777 に答える
1

これらのタイプの質問のほとんどによくあることですが、私は「状況によって異なります」と言います。:)

しかし、抽象クラスとラムダを使用する理由は、実際には動作にあると思います。通常、ラムダはコールバックタイプの機能として使用されていると思います。つまり、何か他のことが起こったときに何かカスタムが起こるようにしたい場合です。私はこれをクライアント側のコードで頻繁に行います:-サービス呼び出しを行います-データを取得します-コールバックを呼び出して、それに応じてそのデータを処理します

ラムダでも同じことができます。ラムダは特定のものであり、非常に特定の状況を対象としています。

抽象クラス(またはインターフェース)を使用することは、実際には、クラスの動作が周囲の環境によって駆動される場所に帰着します。何が起こっているのか、どのクライアントを扱っているのかなど。これらのより大きな質問は、一連の動作を定義してから、開発者(またはAPIのコンシューマー)が要件に基づいて独自の一連の動作を作成できるようにする必要があることを示唆している可能性があります。確かに、ラムダでも同じことができますが、開発がより複雑になり、ユーザーと明確に通信することもより複雑になると思います。

したがって、私の経験則は次のとおりです。-特定のコールバックまたは副作用のカスタマイズされた動作にラムダを使用します。-抽象クラスまたはインターフェースを使用して、オブジェクトの動作をカスタマイズするためのメカニズム(または少なくともオブジェクトの主要な動作の大部分)を提供します。

申し訳ありませんが、明確な定義をお伝えすることはできませんが、これがお役に立てば幸いです。幸運を!

于 2012-06-11T16:23:47.917 に答える
1

考慮すべきいくつかの事項:

いくつの異なる関数/デリゲートをオーバーライドする必要がありますか? 機能する可能性がある場合、継承はオーバーライドの「セット」を理解しやすい方法でグループ化します。単一の「登録」機能があるが、多くのサブ部分を実装者に委任できる場合、これは「テンプレート」パターンの典型的なケースであり、継承するのが最も理にかなっています。

同じ関数の異なる実装がいくつ必要ですか? 1 つだけの場合、継承は適切ですが、実装が多数ある場合は、デリゲートを使用するとオーバーヘッドが節約される可能性があります。

複数の実装がある場合、プログラムはそれらを切り替える必要がありますか? または、単一の実装のみを使用します。切り替えが必要な場合は、デリゲートの方が簡単かもしれませんが、特に #1 への回答によっては注意が必要です。戦略パターンを参照してください。

オーバーライドが保護されたメンバーへのアクセスを必要とする場合は、継承します。パブリックのみに依存できる場合は、委任します。

その他の選択肢は、イベントと拡張メソッドです。

于 2012-06-11T16:27:52.817 に答える