インターフェースA
があるとします。このインターフェースを実装するクラスのインスタンスは、これらのインスタンスが不変であることに依存するアルゴリズムに渡されます。たとえば、指定されたパラメーターに依存する回数だけ、再帰的な方法でA.execute(command)
を呼び出します。 execute()
がインスタンスの状態を変更すると、アルゴリズムは期待どおりに動作しません。
不変性はインターフェイスによって(少なくともJavaでは)強制できないため、コントラクトとしてそれをクラスのドキュメントに記述することを考えています。
これは妥当ですか、それとも実装の詳細を課していますか?
はい、これは間違いなく合理的です。
Streams API を見てください。たとえば、多くの制約はタイプではなくJavaDocsとして表現されます。 Comparator
インターフェースは、タイプではなくドキュメントでほぼすべての制約を指定するもう1つの有名な例です。または、List.add
、そのタイプで実装が実際に要素をリストに追加する必要があることを指定していない、またはList.sort
は、実装で実際にリストをソートする必要があることをそのタイプで指定しない、などです。
Javaプログラマーは、型がすべてのことを伝えるわけではなく、全体像についてはドキュメントを参照する必要があることを知っています。
実装が「こと」を行わないことをインターフェースが指定することは確かに合理的です。このパターンは、C、C++、およびFortranでよく見られます。 precondition (before)、 postcondition (after)、まれにペリコンディション(関数/クラスの実行中に何が起こるか)と呼ばれるものがよく見られます。これは、実際に言語コンストラクトを contracts の形式で実装する言語でさらに拡張されています。多くの場合、C++テンプレート関数の事前条件と事後条件が表示されます。これは、その中で使用できるものと使用できないもの、テンプレートに必要なプロパティ(Javaのジェネリック)などを定義しますが、強制する方法はありません。 (非常にハードなテンプレートメタプログラミングなし)。
ただし、あなたの場合、クラス全体が不変である必要があるかどうか、またはクラス全体ではなく関数のみが不変である必要があるかどうかはわかりません。
あなたの状況では、ドキュメントで定義された制約を提供する代わりに、アクセスしたくないデータを含むクラスを定義することができるかもしれません(それが何であるかさえ知っていると仮定して)アクセサーのみを実装するfinal)そして、そのクラスに "execute"のインターフェースを実装(または抽象メソッドを提供)させます。クラス全体で不変性を保証することはできませんが、知っている値のサブセットにそれを強制することはできます。同じ抽象クラスでfinalキーワードを使用して、同様の効果を実現することもできます(ただし、実現が難しい場合があります)
他の言語(C++など)では、コントラクトがない場合でも、関数を「const」に強制できます。つまり、呼び出し元のクラスを変更しません。 Javaは、そのようなことを達成することを特に困難にします。
必要なのがexecute
で状態を変更しないことだけである場合、インスタンスが不変であることを要求することは合理的ではありません。より複雑なケースでも、インターフェイスのメソッドが状態を変更しないことを要求するだけで十分です。
これらの要件を公開してドキュメント化すると、実装の詳細ではなくなり、公開された仕様または契約に昇格します。