web-dev-qa-db-ja.com

「不変」インターフェース

不変性の概念について混乱しています。次の単純な計算機の構造を考えてみます。

Class diagram

インターフェースは次のとおりです。

interface IOperationalInterface {
    int Sum(int a, int b);
}

interface IAuditInterface {
    int SumInvocations();
}

IOperationalInterface.Sumは2つの整数の合計を計算し、IAuditInterface.SumInvocationsは受信したSum呼び出しの総数を返す必要があります。

これは簡単な実装です。

class CalculatorImpl : IOperationalInterface, IAuditInterface {
    private int invocations = 0;

    public int Sum(int a, int b) {
        invocations++;
        return a + b;
    }

    public int SumInvocations() {
        return invocations;
    }
}

CalculatorImplは不変ですか? Sumメソッドを呼び出すたびに状態が変わるため、明らかにそうではありません。 Sum操作は純粋ですか? Wikipedia によると、変更可能なオブジェクトの状態を変更し、IAuditInterfaceを介してその副作用を観察できるためです。当然、SumInvocations操作も異なる結果を返す可能性があるため、純粋ではありません。

要約すると、CalculatorImplは変更可能であり、そのメソッドはすべて不純です。

ただし、CalculatorClientからのみ通信するIOperationalInterfaceの観点からは、不変であるように見え、Sum操作は、副作用を観察できませんでしたそのインターフェースを介して

一方、AuditClientの観点からは完全に異なります。AuditInterfaceを実装するオブジェクトが変更可能であり、そのSumInvocations操作が不純であることは明らかです。 これはIAuditInterfaceの指定から直接従います。

そのため、変更可能なクラスのインターフェイス/仕様を分割して、一部の部分が変更可能であるように見えるようにしたり、変更できないようにすることができます。この場合、Sumオペレーションのみを考慮し、呼び出しをカウントする必要があるという要件を除外すると、副作用のないものが得られます。

ここで、CalculatorClientの実装時に、インターフェイスの背後にあるオブジェクトappearsが不変であるという事実を考慮することができます。少なくとも1人は違いを見分けることができませんでした。

だから私の質問は:インターフェイスの「不変性」について話すことは理にかなっていますか、それとも悪い考えですか?そのインターフェースを介して観察可能な副作用がないという事実を他にどのように伝えることができますか?そしてそれが悪い場合、何がうまくいかないのでしょうか?

[〜#〜]更新[〜#〜]

あなたの答え/コメントをありがとう! IOperationalInterfaceが純粋であると言う方法がないことがわかりました。この場合、純度の条件が強すぎて適用できません。しかし、適用できるより弱い概念(多分「不変性」?)があるかどうかという問題が残っています。

8
proskor

ただし、IOperationalInterfaceを介してのみ通信するCalculatorClientの観点から、これは不変であるように見え、Sum操作は副作用を観察できなかったという意味で純粋であるように見えますそのインターフェースを通じて =。

インターフェースを介して観察できるものは、純粋性の概念とは無関係です。 Sumが結果をファイルに記録した場合、インターフェースを介して変更を監視することはできませんが、それでも不正確です。

純度は非常に強い状態です。何かが純粋だと言ったら、私はあなたをあなたの言葉に連れて行き、それを複数のスレッドから実行し、結果をキャッシュし、特定の入力セットに対して一度だけ呼び出すか、またはOSのファイルハンドルを使い果たすことなく、1,000,000回。 CalculatorImplを使用する場合、これらの仮定は裏目に出る可能性があります。

あなたが持っている方法でインターフェースを分割することには何の問題もありませんが、あなたは正確でなければなりません。 IOperationalInterfaceに指定する仕様は、おそらく次のようになります。「これらのメソッドの戻り値は、引数のみに依存する必要がありますが、実行によって副作用が生じる可能性があります。」あなたは副作用についてより具体的で、メソッドがどんな種類のI/Oも実行してはならないことを言うことができます。しかし、あなたがそれをそのように使用していないことがはっきりしているので、私はそれが純粋であるとは言いません。

5
Doval

関数が純粋かどうかを気にする理由を気にせずに、定義にこだわるようなものです。純粋な関数は、実装者の制限を犠牲にして、呼び出し側により多くの自由を与えます。副作用のある関数は、呼び出し側の制限を犠牲にして、実装者により多くの自由を与えます。

ほとんどの人は純粋な関数の実装者に対する制限に精通していますが、多くの人はそれが呼び出し元に与える自由に精通していません。呼び出し元を解放して結果をキャッシュし、関数を再度呼び出すことはありません。呼び出し元を解放して、オブジェクトのコピーを必要なだけ作成し、おそらく異なるスレッドまたは異なるシステムでさえ計算を高速化します。これにより、呼び出し側はオブジェクトをスコープ内に保持することを心配する必要がなくなります。これにより、呼び出し元は後で同じオブジェクトをいつでも再作成できます。

これらのタイプの自由を呼び出し元に許可しない場合は、インターフェースに不変のラベルを付けることはできません。呼び出し元に相応の利点なしに実装者に制限を課しているので、実装者の制限を解除することもできます。

5
Karl Bielefeldt

実装は明らかに不変ではないので、Sum関数の「純粋さ」について話しましょう。

インターフェースについてではなく、実装について話すだけで意味があります。たとえば、このSum実装を使用してインターフェースの新しい実装を作成した場合はどうなりますか?

public int Sum(int a, int b) {
    invocations++;
    if(invocations >= 1000)
    {
        return a + b + 1;
    }
    return a + b;
}

インターフェースは変更されていませんが、メソッドはもはや「純粋」ではありません。

純粋に技術的な意味で、最終的にinvocationsがオーバーフローし、予期した結果の代わりにSumを呼び出すと例外が発生するため、単純な実装でも純粋ではありません。

1
Scott Whitlock

私はあなたが望むものは Design by Contract のバリアントであると信じています(C#インターフェースはそれらの貧しい人のバージョンです)。 C#では、カスタム属性と静的アナライザー拡張機能(RoslynやReSharperなど)を介して実現できます。

[Pure] //This is just a sample name, not to be confused with System.Diagnostics.Contracts.PureAttribute
interface IOperationalInterface
{
    int Sum(int a, int b);
}

次に、カスタム静的アナライザー拡張機能は、すべてのインターフェースメソッドで実装に副作用がないかどうかをチェックします。これは、達成したい保証のレベルに応じて複雑になる可能性があります(たとえば、使用される変異演算子がないことを単にチェックするだけでなく、メソッド呼び出しチェーンをウォークするだけです)。

このアプローチがシナリオに適している場合は、.NETの既存の契約関連ツールをチェックすることを検討してください(ただし、これらのツールはどれもうまく機能しません)。

JetBrains: http://www.jetbrains.com/resharper/webhelp/Reference__Code_Annotation_Attributes.html Microsoft: http://research.Microsoft.com/en-us/projects/contracts

1
Den