プログラミング応用a 第8回 『モジュール化その1 (メンバアクセス制御とアクセッサ)』 |
8-1 【変数の一生】
今回のテーマ:『正常に動作するには,変数(フィールド)を外部から守らなくてはならない。』
●「正しく動作するプログラムにおける変数のライフタイム」再掲。
(画像をクリックすると今回の学習と関係するところが表示される)

※今回は特に,上図(3)の「適切な操作による値の変更」について解説する。
【モジュール】
●今回の講義の手始めとして,『モジュール(module)』という考え方を紹介する。
・通常,『製品』は部品から構成されている。それは,ソフトウェアという製品についても同じである。
・ボルトやビス,鉄板,ワイヤといった単純な部品もあるが,こういった部品は単純すぎて意味のあるほどの機能は持っていない。
・そして,これらの細かい部品を組み合わせて,特定の機能を持ったより上位レベルの部品を作ることができる。自動車で言えば,エンジンや変速機,ブレーキ装置などである。
このように,
それなりの意味と機能を持った部品のことをモジュール(module)と呼ぶ。例としては,
自動車のエンジンモジュール,ブレーキモジュール,
コンピュータのメモリモジュール,電源モジュール
などが挙げられる。
・Java では,最少のモジュールはクラスと言える。また,関連した機能を持つクラスを集めて,パッケージ(package)というより上位レベルのモジュールを作ることができる(下図)。

・世の中に存在するほとんどの工業製品がモジュールから構成されているのは,様々な利点があるからである。
ソフトウェアという工業製品も同様に,モジュールという考え方が重要になってくるのでおぼえておこう。
・なぜ,今回のテーマが『正常に動作するには,変数(フィールド)を外部から守らなくてはならない。』であるのに,モジュールの話題が出たのか?
実は,
このテーマはモジュールの考え方と表裏一体なのである。
では,本題に入っていこう。
【名簿クラス】
まず,今回のテーマ『正常に動作するには,変数(フィールド)を外部から守らなくてはならない。』を確認するために,実際の例を見てみよう。
Person型オブジェクトを登録する名簿を考えてみる。ここでは,名簿を表すクラスをMeiboクラスとする(下リスト Meibo.java)。
Meibo.java

※Meiboクラスのmaxnumフィールドにはfinalという指定がついている。final指定付きで宣言されたフィールドは,それ以降値の変更ができなくなる。
つまり,final指定されたフィールドは定数になるわけである。これを,一般に final定数と呼ぶ。
この名簿クラス Meibo をテストするクラス
MeiboTest01.java

※Eclipse で Java用プロジェクトを作成し, 上記の Meibo.java と MeiboTest01.java をそのプロジェクトに追加して,実際に動かし,
その動きを理解しよう。
ちなみに,実行結果は下図のようになる。
実行結果は以下の通り。

さて,次にこのプログラムを少しいじってうまく動かないようにしてみよう。
【クラス外からのアクセス禁止】
この名簿クラスのメンバが外部から勝手にいじられると,名簿クラスは正常に動作しなくなってしまう。その例が AttackMeibo.java (下図)である。
AttackMeibo.java

※この実行結果から,2個目のPerson型オブジェクトが登録できないというバグ(不良動作)が生じている。
※このバグは,1個目の Person型オブジェクトを登録した後, Meibo型オブジェクトmのフィールド num を勝手に 10 に書き換えてしまった(上図の黄色部分)ために起こっている。
※このように,このままではクラスやオブジェクトのメンバが勝手にいじられて,クラスとそのオブジェクトの機能が簡単に阻害される可能性があることがわかる。
※もちろん,上図のように重要な変数の値を勝手に変えないように注意してプログラムを作成すれば良いのだが,ソフトウェア開発は複数人で行うこともあり,プログラム作成担当者が変われば,
この手の注意点は忘れられてしまうかもしれない。
※そこで,Javaには,クラスのメンバを他のクラスのメンバから利用することを禁止する手段が用意されている。次にその具体的な方法を紹介する。
●クラスの外部からメンバへのアクセスを禁じるには,メンバを private 指定する
クラスのメンバを,他のクラスのイタズラから守るには,メンバを private 指定してやれば良い(下図)。

●実際に, Person クラスや Meibo クラスのメンバを private 指定してみよう(下図)。
改良版Meibo.java

このように,フィールドをprivate指定した場合,AttackMeibo.javaの5行目
m.num = 10;
という部分は,コンパイル時に以下のようなメッセージを出してエラーになる。
これは,Meiboクラスの num フィールドが private 指定されているので, AttackMeiboクラスのメンバからはアクセスできないからである(上図右下)。
AttackMeibo.java:5: num は Meibo で private アクセスされます。
m.num = 10; // 登録数をむりやり上限値に設定してしまう
^
この下にもエラーメッセージが続くが,それは次節で説明する
|
※フィールドは,他のクラス(とそのオブジェクト達)から勝手に値をいじられないように, private 指定しておくこと。これはオブジェクト指向プログラミングの常識である.
●一難去ってまた一難?
Miboクラスの全フィールドを private に指定したので, Miboクラスの全フィールドは勝手にアクセスされることはなくなった。
同様に,Personクラスの全フィールドも private 指定されているので, Personクラスの外から勝手にアクセスされることは無い。
しかし,まだ問題が残っている。Person の age, gender, name フィールドを private にしたため, Meibo クラスの printAll( ) メソッ
ドで,これらの private なフィールド
pa[i].age,pa[i].gender,pa[i].name
にアクセスしようとしているところ(Meibo.javaの36行目)も以下のようなメッセージを出してコンパイルエラーになってしまう
のである(!)。
./Meibo.java:36: age は Person で private アクセスされます。
if( pa[i] != null ) System.out.println( pa[i].age + ", " + pa[i].gender + ", " + pa[i].name );
^
./Meibo.java:36: gender は Person で private アクセスされます。
if( pa[i] != null ) System.out.println( pa[i].age + ", " + pa[i].gender + ", " + pa[i].name );
^
./Meibo.java:36: name は Person で private アクセスされます。
if( pa[i] != null ) System.out.println( pa[i].age + ", " + pa[i].gender + ", " + pa[i].name );
^
|
つまり,次のような矛盾が起こっているのである。
・Personクラスとしては,全フィールドを外部からの意図しないアクセスから守るにはprivateにしなければならない
・一方,Meiboクラスは,Person型の各フィールド値を表示するためにそれらのフィールドのアクセスしなければならない
このように,クラスの外部から privateなフィールドにアクセスする必要があるときは,どうすればよいのだろうか?
実は,後述する「アクセッサ」というフィールドにアクセスするための専用のメソッドを用意することで解決できる。
アクセッサについて解説する前に,クラスのメンバが公開部分と非公開部分に分かれる意味を確認しておこう。
【公開と非公開の意味】
●下図のように,モジュールの公開部分は外部とのインタフェイス(interface)の役目を果たす(インタフェイス部)。
●また,モジュールの非公開部分は外部からは隠され,実装部と呼ばれる。

※つまり,モジュールには,実装部分とインタフェイス部分が自然と生まれるのである。
※逆に言うと,モジュールを作ったつもりでも,どの部分が実装部分か,どの部分がインタフェイス部分かを意識して作らないと,モジュールを作成したことにはならない。
※インターフェイス部分は他者がこのモジュールを利用する際に直接アクセスする部分なので,その仕様を頻繁に変更してはならない
。
※逆に,実装部分は外部から隠されているので,比較的自由に変更が可能である。
【問い】
・車や携帯電話など,身近の工業製品のインタフェイス部分と実装部分を具体例を挙げて示せ。それらのインタフェイス部分が変更されたらどう困るかを
議論せよ(例:車のアクセルペダルとブレーキペダルが左右逆になったら?等)。
・自然界の動物にも,インタフェイス部分と実装部分がある。たとえば,脳や内臓は外部から勝手にいじられないように外部からは隠されている。
では,自然界の動物のインタフェイス部分に相当する物は何か。具体例をあげよ。
【アクセッサ】
前述したように,フィールドは原則的にprivateにして,クラスの外部からアクセスできないようにすべきである。
そのため,クラスの外部からフィールドの値を変更したり,フィールドの値を参照したい場合には,そのための専用の
メソッドを用意する。このようなフィールドにアクセスするための専用のメソッドをアクセッサ(accessor)と呼ぶ。
特に,フィールドに値を設定するアクセッサをセッタ(setter),フィールドの値を返すメソッドをゲッタ(getter)と
言う。
次に,アクセッサの例を2つのソースで示す。最初のソース(Accessor1.java,下図)では,クラスAが定義されて
おり, 0 以上でなくてはならない int 型フィールドiを持つ(下図(1))。i はフィールドなのでprivate指定していることに
注意。クラスAには,フィールド i へアクセスするためのアクセッサが2つ用意されている。
メソッドgetI()は,iの値を返してくれるゲッタである。このように,ゲッタの名前は「getフィールド名」という
形をしていることが多い。
また,メソッド setI( ) は, i に値を設定するセッタである。setI( ) は,渡された実引数の値をiに代入するが,実引数が
負の値である場合は, i の値を変更しない。このようにして,setI( ) は「フィールドiの値は 0 以上でなくてはならない」
という条件が常に成立するように,クラスAのオブジェクトを護っているのである。なお,この setI( ) のようにセッタ
の名前は,「setフィールド名」という形になっていることが多い。
下図のAccessor1.java の main( )メソッドを見てみよう。④では,セッタ setI( ) を使い,A型オブジェクトaのフィー
ルド i の値を10に設定している。⑤では,ゲッタ getI( ) を使用してフィールドiの値を取り出して表示するので,10と
表示される。⑥では,またセッタ setI( ) を呼び出してフィールドiに -10 を設定しようとしているが, setI( ) は負の値が
実引数に渡された場合には i の値を変更しないので,⑦で i の値を表示しているところでは,やはり 10 と表示される。
Accessor1.java

次に,今まで頻出してきたPersonクラスにprivate指定とアクセッサを追加した例を示す(Accessor2.java下図)。
ちゃんと,フィールドageとgenderはいずれもprivateになっている(下図(1),(2))。これらのフィールドへのアク
セッサが下図(3)〜(6)である。2つのセッタはいずれも実引数の値が適切なときだけフィールドに実引数の値を代
入している。talk()メソッドのなかでは,2つのセッタを利用してフィールドの値を取り出している(下図(7),(8))。
Accessor2クラスのmain()メソッドでは,2つのセッタを使って各フィールドの値を変更しているが,不正な値は
検知されて無視される(下図(9),(10))。
Accessor2.java

●コンストラクタでセッタを利用する
また,セッターは担当するフィールドに適切な値が設定されるようにチェックをする役目を持つ事が多い。上図のAccessor2.javaの例では,setAge( )
メソッドとsetGender( )メソッドで,それぞれフィールドに代入される値として渡された実引数の値が適切かどうかチェックをしている。
そのため,コンストラクタの初期化処理でも,こうした値のチェックを行うセッタを利用すると良い。この方針に従い,Accessor2.java を書き換えた例を
以下に示す。コンストラクタの初期化処理で setAge( ) と setGender( ) を呼び出している(☆と★の箇所)。
Accessor2.java

なお,ここではコンストラクタに負の年齢値や性別値として不正な値を実引数として渡してオブジェクトを初期化するとエラーメッセージ
を表示して,Person1型オブジェクトのフィールドは適切な初期化が行われないままになってしまう。
そもそも適切な値で初期化されないままオブジェクトを利用するのは危険なので,本来であれば
・なんらかの特別な処理に移行して,適切な値に初期化されるように再チャレンジする
・他のサービスに影響の無いようにこのプログラムを終了する。
といった処置が必要になってくる。このような処置を扱うためには,後期に学習する「例外」という仕組みを知る必要がある。
学習中の皆さんは,とりあえず上記の例のようにエラー表示などで対処しておくようにしておけばいいだろう。
・Meibo.javaのPersonクラスに適切なアクセッサを設けた例を以下に示す。黄色で示したところがアクセッサの定義とその呼び出し箇所である。
改良版その2Meibo.java

※PersonのセッタsetAge(),setGender()では実引数の値が適切かどうかをチェックしてからフィールド
の値をセットしていることに注意。適切な値でない場合,エラーメッセージを表示するようにしても良い
だろう。
※Personのコンストラクタの中でもセッタを利用している。これによって,初期化値が適切かどうかのチェ
ックができる。
アクセッサを追加した 改良版その2Meibo.java のを実際に試すと,コンパイル時に次のようにエラーが1個表示されてコンパイルに失敗する。
AttackMeibo.java:5: num は Meibo で private アクセスされます。
m.num = 10; // 登録数をむりやり上限値に設定してしまう
^
|
以下に,オブジェクト指向の鉄則・常識を示す。
※フィールドは必ず private に指定する
※クラス外部からフィールドを参照したり操作する場合は,必要に応じて専用のメソッド(アクセッサ, accessor)を用意すること