プロパティ (プログラミング)

プロパティ: property) はオブジェクト指向プログラミング言語において、フィールドの操作と同じ構文でアクセッサーを定義するための言語機能および構文の一種である。

概要 編集

オブジェクト指向プログラミングにおいてオブジェクトの操作を共通化することは極めて重要であり核である。クラスが異なるオブジェクトの間で操作を共通化しようとする際、特定のオブジェクトにしか存在しないフィールドを直接操作する処理が存在すると共通化の邪魔になってしまう。このためオブジェクト指向プログラミングではアクセッサーと呼ばれるメソッド経由でフィールドを操作するというのが定石となっている。しかし、当面の間同じフィールドしか操作しない状態でアクセッサーを定義するのは手間である。また、メソッドの呼び出し方がフィールドと同じであればわざわざ必要も無いときにアクセッサーを用意しなくてもよい。そこでObject Pascal (Delphi)で導入されたのがプロパティである。[1]

構文例 編集

Object Pascalを例として以下にプロパティの構文例を示す。プロパティは下記の使用例のValue1の様にクラス外からは公開したフィールドFValue2とまったく同じ様に操作することができる。しかし、プロパティの定義では下記の例のように実体をメソッドの呼び出しとして実装したり、読み書きに制限を設けたり、フィールドの値を操作することには変わりないものの派生型で再定義可能にしたり別名にしたりと柔軟性をもたせることができる。

type
    TValueHolder = class
        private
            FValue1: Integer;
            
        protected
            function GetValue: Integer;
            procedure SetValue( aValue: Integer );
        
        public
            FValue2: Integer;
        
        published
            // プロパティの定義例
            
            // メソッドとして実装したプロパティ
            // Value1に対する操作がGetValueとSetValueの操作となる
            property Value1: Integer read GetValue write SetValue;
            // 読み取り専用にしたプロパティ
            property Value2: Integer read FValue1;
            // 書き込み専用にしたプロパティ
            property Value3: Integer write FValue1;
            // フィールドの読み書きに特化したプロパティ
            // (派生型で再定義しなければほぼフィールドと同じ)
            property Value4: Integer read FValue1 write FValue1;
    end;

function TValueHolder.GetValue: Integer;
begin
    Result := FValue1;
end;

procedure TValueHolder.SetValue( aValue: Integer );
begin
    FValue1 := aValue.
end;

var
    Example: TValueHolder;
    ResultValue: Integer;
begin
    Example := TValueHolder.Create;
    
    // プロパティの使用例
    Example.Value1 := 0;            // SetValueの呼び出しとなる
    ResultValue := Example.Value1;  // GetValueの呼び出しとなる
    
    // 参考:フィールドの直接操作
    Example.FValue2 := 0;
    ResultValue := Example.FValue2;
end;

フィールドのプロパティ化 編集

概要で述べた通りフィールドを直接操作してしまうと下記の例の様にフィールドが存在しないオブジェクトを共通の関数(例ではCount)を再利用することができなくなってしまう。これを防ぐためオブジェクト指向プログラミングではアクセッサーを用いるが、プロパティが存在しない言語では一度公開してしまったフィールドをアクセッサーに変換するのは、フィールドを操作している箇所全てを修正する必要が発生するため容易ではない。このため、将来的に共通化する必要が出てくるか否かに関わらずアクセッサーを定義することが鉄則となっている。


■フィールド直接操作による問題の具体例(Object Pascal)

// フィールドを直接操作する汎用関数の例
function Count<T>( Collection : T ): Integer;
begin
    // 例示用であり現実的に意味のある処理ではない。またfCountも公開されてはいない。
    Result := Collection.FCount // フィールドを直接参照
end;

// 呼び出し側
// ◯:"TArrayにfCount"があるので構文エラーにならない
Count( TArray<Integer>.Create );
// ✗:"TLinkedListにfCount"がないので構文エラーとなる
Count( TLinkedList<Integer>.Create );


// アクセッサー経由で操作する汎用関数の例
function Count<T>( Collection : T ): Integer;
begin
    // 例示用であり現実的に意味のある処理ではない。
    Result := Collection.Count // アクセッサー(プロパティ)経由で参照
end;

// 呼び出し側
// どちらも構文エラーとならない。
Count( TArray<Integer>.Create );
Count( TLinkedList<Integer>.Create );

反面プロパティを持つ言語においてはソースコード互換の範囲ではあるがフィールドからアクセッサーへの変更が極めて容易となる。下記の例では、フィールドとして公開していたValueを削除してプロパティであるValueに置換しているが、プロパティがフィールドと互換性を持つためフィールドを操作していた箇所には一切変更が発生しない。これによりプロパティが存在する言語ではとりあえずはフィールドを公開しておき必要なってきたらプロパティに変更していくという運用が可能になっている。


■プロパティ化(Object Pascal)

type
    // Valueをプロパティ化
    TValueHolder = class
        private
            // 追加
            FValue: Integer;
            
        public
            // 削除
            //Value: Integer;
        
        published
            // 追加
            property Value: Integer read FValue write FValue;
    end;

var
    Example: TValueHolder;
    ResultValue: Integer;
begin
    Example := TValueHolder.Create;
    
    // プロパティ化しても一切変更は必要ない
    Example.Value := 0;
    ResultValue := Example.Value;
end;

なお、フィールドに制約を掛けやすい言語ほど運用性が高まる。フィールドをConst等で修飾しオブジェクト外部からは読み取り専用とできるような言語であればフィールドの書き込み操作を気にすることなくフィールドを読み取り専用のプロパティに変更することができる。Objective-Cではより進んでおりプロパティ宣言とフィールド宣言が一体化しており、最も単純なプロパティは派生型で再定義可能かどうかを除いてフィールドと変わらない。[2]

@property float value;

Objective-Cと互換性のあるSwiftでは格納方法の指定方法が属性の定義の中(下記であれば@SmallNumberの定義の中)に含まれるようになり[3]、よりフィールドとプロパティの融合が進んでいる。

@SmallNumber var value: Int

サポート言語 編集

その他言語の例 編集

C# 編集

C#においては、クラスおよび構造体の内部にプロパティを持つことができる。

明示的なバッキングフィールドを持つプロパティ 編集

明示的にバッキングフィールド (backing field) を記述する基本的なプロパティの例を示す。

プロパティの構文内ではget, setが、またsetアクセサ内ではvalueがコンテキスト(文脈)キーワードとして機能する。

  class Person {
      /// バッキングフィールド
      private string name;

      /// nameをカプセル化するプロパティ
      public string Name {
          get { return name; }
          set {
              if (value == null) { throw new ArgumentNullException(); }
              name = value;
          }
      }
  }

呼び出し側では、フィールドにアクセスするような構文でプロパティの機能を呼び出す。

  var person = new Person();
  // set アクセス
  person.Name = "John";
  // get アクセス
  string userName = person.Name;
自動実装プロパティ 編集

値の取得と設定のみを行い、取得設定時に追加の処理を行わない場合、自動実装プロパティ (auto-implemented properties) と呼ばれる簡易な構文を用いることができる。

自動実装プロパティでは、バッキングフィールドが内部的に自動生成される。 プログラマは自動生成されたバッキングフィールドには直接アクセスすることはできない。

  class Person {
      // バッキングフィールドが(不可視ではあるが)自動生成されている
      public string Job { get; set; }
  }

参考文献 編集

  1. ^ Properties (Delphi) - RAD Studio”. docwiki.embarcadero.com. 2023年10月13日閲覧。
  2. ^ Declared Properties”. developer.apple.com. 2023年10月13日閲覧。
  3. ^ Documentation”. docs.swift.org. 2023年10月16日閲覧。

関連項目 編集