プロパティ
プロパティを使用すると、メンバー関数をメンバー変数のように使用することができる。
この機能は、スライスでよく知られている。スライスのlength
プロパティは、そのスライスの要素数を返す。
この使用方法だけを見ると、.length
がメンバー変数として実装されているように思えるかもしれない:
しかし、このプロパティの他の機能を見ると、これがメンバー変数ではないことがわかる。.length
プロパティに新しい値を代入すると、実際にはスライスの長さが変わり、場合によっては基になる配列に新しい要素が追加される。
注釈:固定長配列の.length
プロパティは変更できない。
上記の.length
への代入は、単純な値の変更よりも複雑な操作を伴う。配列に新しい長さの容量があるかどうかを判断し、ない場合はメモリを追加で割り当て、既存の要素を新しい場所に移動し、最後に.init
によって追加された各要素を初期化する。
明らかに、.length
への代入は関数のように動作する。
プロパティは、メンバー変数のように使用されるメンバー関数である。
括弧を使用しない関数の呼び出し
前の章で述べたように、渡す引数がない場合、関数は括弧なしで呼び出すことができる。
この機能は、プロパティはほとんどの場合括弧なしで使うため、プロパティと密接に関連している。
値を返すプロパティ関数
簡単な例として、2つのメンバーで構成される長方形構造体を考えてみよう。
この型に、長方形の面積を提供する3番目のプロパティが必要になったとしよう。
この要件を満たす一つの方法は、3つ目のメンバーを定義することだ:
この設計の欠点は、オブジェクトが容易に一貫性を失う可能性があることだ。長方形は常に"幅 × 高さ == 面積"という不変条件を満たす必要があるが、メンバーが自由に独立して変更可能だと、この一貫性が破られる可能性がある。
極端な例としては、オブジェクトが矛盾した状態で誕生することさえあり得る。
より良い方法は、面積の概念をプロパティとして表現することだ。追加のメンバーを定義する代わりに、そのメンバーの値は、それが表す概念と同じ名前の関数area
によって計算される。
注釈: const ref
パラメータとconst
メンバー関数の章で説明したように、関数宣言のconst
指定子は、この関数によってオブジェクトが変更されないことを保証する。
このプロパティ関数により、構造体は3番目のメンバー変数があるかのように使用することができる。
area
プロパティの値は、長方形の高さと幅を乗算して計算されるため、今回は常に一貫性がある。
庭の面積 | 200 |
---|
代入で使用されるプロパティ関数
スライスのlength
プロパティと同様に、ユーザー定義型のプロパティも代入操作で使用することができる。
この代入によって実際に長方形の面積を変更するには、構造体の2つのメンバーを適宜変更する必要がある。この機能を有効にするには、長方形が柔軟であり、"width * height == area"という不変条件を維持するために、長方形の辺を変更できると仮定する。
このような代入構文を可能にする関数も、area
という名前である。代入の右側で使用される値は、この関数の唯一のパラメータになる。
area()
を次のように追加定義すると、代入操作でこのプロパティを使用し、Rectangle
オブジェクトの面積を効果的に変更できるようになる。
新しい関数は、std.math
モジュールにあるsqrt
関数を利用しており、指定された値の平方根を返す。長方形の幅と高さが、その比率の平方根でスケーリングされた場合、面積は希望する値になる。
その結果、現在の値の4分の1をarea
に割り当てると、長方形の2辺の長さが半分になる。
庭の面積 | 200 |
---|---|
新しい状態 | 5 x 10 = 50 |
プロパティは必ずしも必要ではない
上記では、Rectangle
が3番目のメンバー変数があるかのように使用できることを紹介した。ただし、プロパティの代わりに通常のメンバー関数を使用することもできる。
さらに、関数のオーバーロードの章で見たように、これら2つの関数は同じ名前でもかまわない。
使用タイミング
通常のメンバー関数とプロパティのどちらを選ぶべきかは、簡単には決められないかもしれない。通常のメンバー関数の方が自然である場合もあれば、プロパティの方が自然である場合もある。
しかし、カプセル化と保護属性の章で見たように、メンバー変数への直接アクセスを制限することは重要だ。ユーザーコードがメンバー変数を自由に変更できると、必ずコードのメンテナンスに問題が発生する。そのため、メンバー変数は、通常のメンバー関数またはプロパティ関数によってカプセル化することをお勧めする。
width
やheight
などのメンバーをpublic
アクセス可能のままにしておくことは、非常に単純な型の場合にのみ許容される。ほとんどの場合、プロパティ関数を使用する方法の方が優れた設計だ。
メンバーが、対応するプロパティ関数からのみアクセスできるように、private
になっていることに注意。
また、メンバー関数との名前が混同されないように、メンバー変数の名前に_
文字が追加されていることに注意。メンバー変数の名前に装飾を施すことは、オブジェクト指向プログラミングでは一般的な手法である。
このRectangle
の定義では、width
とheight
がメンバー変数として表示されていることに注意しよう:
メンバー変数を変更するプロパティ関数がない場合、そのメンバーは外部からは事実上読み取り専用になる。
これは、メンバーの改変を制御するために重要だ。メンバー変数は、オブジェクトの一貫性を確保するために、Rectangle
型自体によってのみ変更できる。
後で、メンバー変数を外部から変更できるようにすることが妥当になった場合は、そのメンバーに対して別のプロパティ関数を定義するだけで済む。
@property
プロパティ関数は、@property
属性を使用して定義することもできる。ただし、ベストプラクティスとしては、この属性の使用は推奨されない。
@property
属性の唯一の効果は、構文上プロパティ関数の呼び出しである可能性のある式の型を決定する場合だけだ。以下の出力でわかるように、式f.a
とf.b
の型は異なる。
int ← 式f.aの型(戻り値の型)
const int() ← メンバー関数Foo.bの型