クラス不変
コンピュータプログラミング、特にオブジェクト指向プログラミングにおいて、クラス不変条件(または型不変条件)は、クラスのオブジェクトを制約するために用いられる不変条件です。クラスのメソッドは不変条件を保持する必要があります。クラス不変条件は、オブジェクトに格納される状態を制約します。
クラス不変条件は構築時に確立され、パブリックメソッドの呼び出し間で常に維持されます。パブリック関数が終了する前に不変条件が復元される限り、関数内のコードは不変条件を破ることがあります。並行処理では、メソッド内で不変条件を維持するには、通常、ミューテックスを使用して状態をロックすることでクリティカルセクションを確立する必要があります。
オブジェクト不変条件、または表現不変条件は、オブジェクトの状態に関わらず損なわれない不変プロパティの集合からなるコンピュータプログラミング構造です。これにより、オブジェクトは常に定義済みの条件を満たすことが保証され、メソッドは不正確な推測を行うリスクなしに常にオブジェクトを参照できるようになります。クラス不変条件を定義することで、プログラマーとテスターはソフトウェアテスト中により多くのバグを検出できるようになります。
クラス不変条件と継承
オブジェクト指向ソフトウェアにおけるクラス不変条件の有用な効果は、継承の存在によってさらに高まります。クラス不変条件は継承されます。つまり、「あるクラスのすべての親クラスの不変条件が、そのクラス自体に適用される」のです。[1]
継承により、子孫クラスは親クラスの実装データを変更できるようになるため、子孫クラスがインスタンスの状態を、親クラスの観点から無効となるように変更することが可能になります。このような不適切な動作をする子孫クラスへの懸念は、オブジェクト指向ソフトウェア設計者が継承よりも合成を優先する理由の一つです(つまり、継承はカプセル化を破ります)。[2]
しかし、クラス不変条件は継承されるため、特定のクラスのクラス不変条件は、そのクラスに直接記述された不変条件アサーションと、そのクラスの親から継承されたすべての不変条件節の組み合わせで構成されます。つまり、子クラスが親クラスの実装データにアクセスできる場合でも、クラス不変条件によって、実行時に無効なインスタンスを生成するような方法でデータが操作されることを防ぐことができます。
プログラミング言語のサポート
アサーション
Python、[3] PHP、[4] JavaScript、[5] C++、Javaなどの一般的なプログラミング言語は、デフォルトでアサーションをサポートしており、これを使用してクラスの不変条件を定義できます。クラスに不変条件を実装する一般的なパターンは、クラスのコンストラクタが不変条件が満たされない場合に例外をスローすることです。可変クラスの場合、例えばセッターメソッドが存在する場合や、他の可変オブジェクトに依存している場合など、変更のたびに不変条件が正しく再確立されたかどうかを確認する必要がある場合があります。これにより、メソッドの実装がクラスの不変条件を実際に保持することが保証されます。
ネイティブサポート
クラス不変条件は、契約による設計(Design by Contract)の不可欠な要素です。したがって、Eiffel、Ada、Dafny、Dなど、契約による設計(Design by Contract)をネイティブで完全にサポートするプログラミング言語は、クラス不変条件も完全にサポートします。
非ネイティブサポート
C++の場合、Loki ライブラリは、クラス不変条件、静的データ不変条件、および例外安全性をチェックするためのフレームワークを提供します。
Java には、クラス不変条件をより堅牢に定義する方法を提供する、 Java モデリング言語と呼ばれるより強力なツールがあります。
例
ネイティブサポート
エイダ
Adaプログラミング言語は、型不変条件(および事前条件と事後条件、サブタイプの述語など)をネイティブでサポートしています。型不変条件は、プライベート型(たとえば、その抽象プロパティ間の関係を定義するため)または完全な定義(通常は型の実装の正しさを検証するために)に指定できます。[6] 以下は、論理スタックを表すために使用されるプライベート型の完全な定義に指定された型不変条件の例です。実装では配列を使用し、型不変条件は安全性の証明を可能にする実装の特定のプロパティを指定します。この場合、不変条件は、論理深さNのスタックに対して、配列の最初のN要素が有効な値であることを保証します。Stack型のDefault_Initial_Conditionは、空のスタックを指定することにより、不変条件の初期の真を保証し、Pushは不変条件を保存します。不変式の真偽により、Popはスタックトップが有効な値であるという事実に依拠することができ、これはPopの事後条件を証明するために必要となります。より複雑な型不変式であれば、Popが対応するPushに渡された値を返すといった完全な機能的正しさの証明が可能になりますが、今回のケースでは、PopがInvalid_Valueを返さないことを証明しようとしているだけです。
ジェネリック 型 Item は プライベートです 。Invalid_Value :はItem内にあります。パッケージStacksは型Stack ( Max_Depth : Positive )がプライベートで、Default_Initial_Condition => Is_Empty ( Stack )です。 function Is_Empty ( S : in Stack ) return Boolean ; function Is_Full ( S : in Stack ) return Boolean ; procedure Push ( S : in out Stack ; I : in Item ) with Pre => not Is_Full ( S ) and then I /= Invalid_Value , Post => not Is_Empty ( S ); procedure Pop ( S : in out Stack ; I : out Item ) with Pre => not Is_Empty ( S ) and Post => not Is_Full ( S ) and then I /= Invalid_Value ; private type Item_Array is array ( Positive range <>) of Item ; type Stack ( Max_Depth : Positive ) は、 レコード Length : Natural := 0 ; Data : Item_Array ( 1 .. Max_Depth ) := ( others => Invalid_Value );レコード の終了 Type_Invariant => Length <= Max_Depthであり、then ( for all J in 1 .. Length => Data ( J ) /= Invalid_Value ); function Is_Empty ( S : in Stack ) return Boolean is ( S . Length = 0 ); function Is_Full ( S : in Stack ) return Boolean is ( S . Length = S . Max_Depth ); end Stacks ;D
D言語は、クラス不変条件やその他の契約プログラミング技法をネイティブにサポートしています。以下は公式ドキュメントからの例です。[7]
クラスDate { int日; int時間; 不変式() { assert (日>= 1 &&日<= 31 ); assert (時間>= 0 &&時間<= 23 ); } } エッフェル
Eiffelでは、クラス不変条件は、キーワード に続くクラスの末尾に表示されますinvariant。
クラス日付作成する機能{ NONE } -- 初期化 make ( a_day : INTEGER ; a_hour : INTEGER ) -- `Current' を `a_day' と `a_hour' で初期化します。require valid_day : a_day >= 1 and a_day <= 31 valid_hour : a_hour >= 0 and a_hour <= 23 do day := a_day hour := a_hour ensure day_set : day = a_day hour_set : hour = a_hour end 機能-- アクセス day : INTEGER -- 「現在の」月の日 hour : INTEGER -- 「現在の」時刻 機能-- 要素の変更 set_day ( a_day : INTEGER ) -- `day' を `a_day' に設定するrequire valid_argument : a_day >= 1 and a_day <= 31 do day := a_day ensure day_set : day = a_day end set_hour ( a_hour : INTEGER ) -- `hour' を `a_hour' に設定するrequire valid_argument : a_hour >= 0 and a_hour <= 23 do hour := a_hour ensure hour_set : hour = a_hour end 不変valid_day : day >= 1かつday <= 31 valid_hour : hour >= 0かつhour <= 23終了 非ネイティブサポート
C++
Loki (C++)ライブラリは、クラス不変条件、静的データ不変条件、および例外安全性レベルをチェックするための、Richard Sposato によって作成されたフレームワークを提供します。
これは、クラスがLoki::Checkerを使用して、オブジェクトが変更された後も不変条件が満たされていることを検証する方法の例です。この例では、geopointオブジェクトを使用して、地球上の位置を緯度と経度の座標として保存しています。
ジオポイント不変量は次のとおりです。
- 緯度は北緯 90 度を超えてはなりません。
- 緯度は南 -90° 未満であってはなりません。
- 経度は東経180度を超えてはなりません。
- 経度は西経 -180° 未満であってはなりません。
#include <loki/Checker.h> <cassert>をインポートします。 import org . wikipedia . examples . Degrees ; org :: wikipedia :: examples :: Degreesを使用します。 /** * クラスの不変条件をチェックするために必要です。* @note CheckFor は、多くの関数で妥当性チェックを実行して、コードが不変条件に違反していないか、コンテンツが変更されたか、または関数が例外をスローしたかどうかを判断します。*/ using Loki :: CheckFor ; class GeoPoint { private : /// この関数はすべてのオブジェクトの不変条件をチェックします。bool isValid () const { assert ( this ); assert ( latitude >= -90.0 ); assert ( latitude <= 90.0 ) ; assert ( longitude >= -180.0 ); assert ( longitude <= 180.0 ); return true ; } 緯度度; ///< 赤道からの度。正は北、負は南。経度度; ///< 本初子午線からの度。正は東、負は西。public : GeoPoint (緯度度,経度度) ; /// Move関数はGeoPointの位置を移動します。void move ( Degrees latChange , Degrees lonChange ) { // チェッカーオブジェクトは関数の入口と出口でIsValidを呼び出して、このGeoPointオブジェクトが有効であることを証明します。また、チェッカーはGeoPoint::move関数が例外をスローしないことを保証します。CheckFor < const GeoPoint > :: CheckForNoThrow checker ( this , & isValid ); latitude_ += latChange ; if (緯度>= 90.0f ) {緯度= 90.0f ; } if (緯度<= -90.0f ) {緯度= -90.0f ; } 経度+= lonChange ; while (経度>= 180.0f ) {経度-= 360.0f ; } while (経度<= -180.0f ) {経度+= 360.0f ; } } } ジャワ
これは、Javaプログラミング言語とJavaモデリング言語におけるクラス不変条件の例です。この不変条件は、コンストラクタの終了後、およびすべてのパブリックメンバー関数の入口と出口で成立する必要があります。パブリックメンバー関数は、クラス不変条件を保証するために、事前条件と事後条件を定義する必要があります。
パブリッククラスDate { int /*@spec_public@*/日; int /*@spec_public@*/時間; /*@invariant day >= 1 && day <= 31; @*/ //クラス不変/*@invariant hour >= 0 && hour <= 23; @*/ //クラス不変 /*@ @requires d >= 1 && d <= 31; @requires h >= 0 && h <= 23; @*/ public Date ( int d , int h ) { // コンストラクターday = d ; hour = h ; } /*@ @requires d >= 1 && d <= 31; @ensures day == d; @*/ public void setDay ( int d ) { day = d ; } /*@ @requires h >= 0 && h <= 23; @ensures hour == h; @*/ public void setHour ( int h ) { hour = h ; } } 参照
- 不変式に基づくプログラミング – プログラミングの方法論
参考文献
- ^ マイヤー、ベルトラン『オブジェクト指向ソフトウェア構築』第2版、プレンティスホール、1997年、570ページ。
- ^ E. Gamma, R. Helm, R. Johnson, J. Vlissides.『デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素』 Addison-Wesley, Reading, Massachusetts, 1995., p. 20.
- ^ 公式 Python ドキュメント、assert ステートメント
- ^ 「PHP assert関数」。2001年3月21日時点のオリジナルよりアーカイブ。
- ^ "console: assert() 静的メソッド - Web API | MDN". MDN Web Docs . 2025年7月4日. 2025年8月24日閲覧。
- ^ 「Adaリファレンスマニュアル7.3.2型不変条件」ada-auth.org . 2022年11月27日閲覧。
- ^ 「契約プログラミング - Dプログラミング言語」dlang.org . 2020年10月29日閲覧。