web-dev-qa-db-ja.com

どのプロパティが値を変更する可能性があり、どれが一定のままであるかが明確になるように、インターフェイスをどのように設計しますか?

.NETプロパティに関する設計上の問題があります。

_interface IX
{
    Guid Id { get; }
    bool IsInvalidated { get; }
    void Invalidate();
}
_

問題:

このインターフェイスには、2つの読み取り専用プロパティIdIsInvalidatedがあります。ただし、それらが読み取り専用であること自体は、それらの値が一定であるという保証はありません。

それを非常に明確にすることが私の意図であったとしましょう…

  • Idは定数値(したがって安全にキャッシュできる)を表しますが、
  • IsInvalidatedは、IXオブジェクトの存続期間中に値を変更する可能性があります(したがって、キャッシュしないでください)。

_interface IX_を変更して、その契約を十分に明示的にするにはどうすればよいですか?

ソリューションでの私自身の3つの試み:

  1. インターフェースはすでにうまく設計されています。 Invalidate()と呼ばれるメソッドの存在により、プログラマーは、類似した名前のプロパティIsInvalidatedの値がその影響を受ける可能性があると推測できます。

    この引数は、メソッドとプロパティの名前が同じである場合にのみ保持されます。

  2. このインターフェイスをイベントIsInvalidatedChangedで拡張します。

    _bool IsInvalidated { get; }
    event EventHandler IsInvalidatedChanged;
    _

    IsInvalidatedの_…Changed_イベントの存在は、このプロパティがその値を変更する可能性があることを示し、Idの同様のイベントが存在しないことは、そのプロパティがそのプロパティを変更しないことを約束します値。

    私はこのソリューションが好きですが、まったく使用できない可能性がある追加の要素がたくさんあります。

  3. プロパティIsInvalidatedをメソッドIsInvalidated()に置き換えます。

    _bool IsInvalidated();
    _

    これは微妙な変更かもしれません。毎回新たに値が計算されるというヒントになるはずです。定数の場合は必要ありません。 MSDNトピック "プロパティとメソッド間の選択" は、これについて次のように述べています。

    以下の状況では、プロパティではなくメソッドを使用してください。 […]パラメータが変更されていなくても、呼び出されるたびに異なる結果が返されます。

どのような答えが期待できますか?

  • 私は、問題に対するまったく異なる解決策と、それらが私の上記の試みにどのように打ち勝ったかについての説明に最も興味があります。

  • 私の試みに論理的な欠陥があるか、まだ言及されていない重大な欠点があり、解決策が1つしか残っていない(またはまったく解決されていない)場合は、どこで問題があったのかを知りたいと思います。

    欠陥が軽微で、考慮に入れても解決策が複数ある場合は、コメントしてください。

  • 少なくとも、どちらがあなたの好ましい解決策であり、どのような理由であるのかについてフィードバックをお願いします。

12
stakx

1と2ではなくソリューション3を使用します。

ソリューション1の私の問題はInvalidateメソッドがない場合はどうなりますか? _DateTime.Now > MyExpirationDate_を返すIsValidプロパティを持つインターフェースを想定します。ここでは明示的なSetInvalidメソッドは必要ないかもしれません。メソッドが複数のプロパティに影響する場合はどうなりますか? IsOpenおよびIsConnectedプロパティを持つ接続タイプを想定します。どちらもCloseメソッドの影響を受けます。

ソリューション2:イベントの唯一の目的が、同様の名前のプロパティが各呼び出しで異なる値を返す可能性があることを開発者に通知することである場合、私はそれに対して強くアドバイスします。インターフェイスは短く明確にしておく必要があります。さらに、すべての実装がそのイベントを起動できるわけではありません。上記のIsValidの例を再利用します。タイマーを実装し、MyExpirationDateに到達したときにイベントを発生させる必要があります。結局のところ、そのイベントがパブリックインターフェイスの一部である場合、インターフェイスのユーザーはそのイベントが機能することを期待します。

それはこれらの解決策について言われている:それらは悪くない。メソッドまたはイベントの存在は、同様の名前のプロパティが各呼び出しで異なる値を返す可能性があることを示します。私が言っているのは、それらだけでは常にその意味を伝えるのに十分ではないということです。

ソリューションは私が目指していることです。 avivが述べたように、これはC#開発者にしか機能しないかもしれません。 C#-devとしての私にとって、IsInvalidatedがプロパティではないという事実は、「単なるアクセサーではなく、ここで何かが起こっている」という意味をすぐに伝えます。ただし、これはすべての人に当てはまるわけではなく、MainMaが指摘したように、.NETフレームワーク自体はここでは一貫していません。

ソリューション3を使用したい場合は、それを規則として宣言し、チーム全体にそれに従うことをお勧めします。 Documentationも必要だと思います。私の意見では、値の変更を示唆することは実際には非常に簡単です: "値がstill validであればtrueを返します"、 " 接続がすでに開かれているかどうかを示します "対"これがtrueを返しますオブジェクトis有効 "。ドキュメンテーションでより明示的にしても、害はありません。

だから私のアドバイスは:

ソリューション3を慣例として宣言し、それに従ってください。プロパティのドキュメントで、プロパティに変化する値があるかどうかを明確にします。単純なプロパティを扱う開発者は、それらが不変であると正しく想定します(つまり、オブジェクトの状態が変更されたときにのみ変更されます)。プロパティのように聞こえるメソッドに遭遇した開発者(Count()Length()IsOpen())は、何かが起こっていることを知っており、(うまくいけば)メソッドのドキュメントを読んでいますメソッドの正確な動作と動作を理解するため。

2
enzi

4番目の解決策があります:ドキュメントに依存

stringクラスが不変であることをどうやって知っていますか? MSDNのドキュメントを読んだので、あなたは単にそれを知っています。一方、StringBuilderは変更可能です。これも、ドキュメンテーションに記載されているためです。

/// <summary>
/// Represents an index of an element stored in the database.
/// </summary>
private interface IX
{
    /// <summary>
    /// Gets the immutable globally unique identifier of the index, the identifier being
    /// assigned by the database when the index is created.
    /// </summary>
    Guid Id { get; }

    /// <summary>
    /// Gets a value indicating whether the index is invalidated, which means that the
    /// indexed element is no longer valid and should be reloaded from the database. The
    /// index can be invalidated by calling the <see cref="Invalidate()" /> method.
    /// </summary>
    bool IsInvalidated { get; }

    /// <summary>
    /// Invalidates the index, without forcing the indexed element to be reloaded from the
    /// database.
    /// </summary>
    void Invalidate();
}

番目のソリューションは悪くありませんが、.NET Frameworkはそれに従いません。例えば:

StringBuilder.Length
DateTime.Now

プロパティですが、常に一定であるとは限りません。

.NET Framework全体で一貫して使用されているのは次のとおりです。

IEnumerable<T>.Count()

メソッドですが、

IList<T>.Length

プロパティです。前者の場合、何かを行うには、たとえばデータベースへのクエリなど、追加の作業が必要になる場合があります。この追加作業には時間がかかる場合があります。プロパティの場合、short時間かかることが予想されます。実際、リストの長さの間に長さが変わる可能性がありますが、実際には何も返さない。


クラスを使用すると、コードを見るだけで、オブジェクトの存続期間中にプロパティの値が変化しないことが明確に示されます。例えば、

public class Product
{
    private readonly int price;

    public Product(int price)
    {
        this.price = price;
    }

    public int Price
    {
        get
        {
            return this.price;
        }
    }
}

明確です:価格は変わりません。

悲しいことに、同じパターンをインターフェイスに適用することはできません。そのような場合、C#は十分に表現力がないため、ドキュメント(XMLドキュメントとUML図を含む)を使用してギャップを埋める必要があります。

8

私の2セント:

オプション3:C#以外のユーザーとしては、それほど手掛かりにはなりません。しかし、あなたが持っている引用から判断すると、これが何かを意味することは熟練したC#プログラマーには明らかです。ただし、これに関するドキュメントを追加する必要があります。

オプション2:変更される可能性のあるすべてのものにイベントを追加する予定がない限り、そこに移動しないでください。

別のオプション:

  • インターフェースの名前をIXMutableに変更すると、状態を変更できることが明確になります。この例の唯一の不変の値はidで、これは通常、可変オブジェクトでも不変であると想定されています。
  • それについては触れません。インターフェイスは、私たちが望むように動作を説明するのが得意ではありません。一部には、この言語では「このメソッドは常に特定のオブジェクトに対して同じ値を返す」、「このメソッドを引数x <0で呼び出すと例外がスローされる」などの記述ができないためです。
  • ただし、言語を拡張し、構成要素についてより正確な情報を提供するためのメカニズムAnnotations/Attributesがあります。いくつかの作業が必要になることもありますが、これにより、メソッドについて複雑なことを任意に指定できます。 「このメソッド/プロパティの値はオブジェクトの存続期間を通じて変化する可能性があります」という注釈を見つけて発明し、それをメソッドに追加します。実装メソッドがそれを「継承」するためにいくつかのトリックを実行する必要がある場合があり、ユーザーはそのアノテーションのドキュメントを読む必要がある場合がありますが、これはヒントの代わりに、言いたいことを正確に言うことができるツールになりますそれで。
4
aviv