インスタンス変数を扱うときは,一声かけて,カギかけて
前回は,インスタンス変数を使って,インスタンス個々に独自の情報を管理できる仕組みをご紹介しました。また,インスタンス変数は,そのまま「プロパティ」として利用できることもわかりましたね。今回は,この変数や関数(プロパティやメソッド)を「隠す」方法をご紹介します。さらに,「隠す」ことによって生まれる,プログラミング上のメリットを併せてご紹介します。このような処理を「カプセル化」と呼びます(図1)。
図1: カプセル化のイメージ(クリックするとムービーを表示します)
情報を「隠す」のは,なにもやましいことがある場合だけではありません。情報をやり取りする相手のことを「思いやって」わかりやすく,簡潔に必要な情報だけを公開する仕組みなのです(*1)。
カプセル化していないクラスの弱点
皆さんは,自分が管理していたと思っていた情報や備品,データが,いつの間にか無くなっていたりしたことはありませんか? 時にはその原因を突き止めるだけで半日くらいはすっ飛んでしまうこともあるでしょう。これは非常に困ります(*2)。このような事態に陥らないためには,シンプルなルールを一つだけ守ればOKです。そう「他人の管理している物を使う前には一声かける」というルールです。プログラムの世界でも,このルールと非常によく似た考えで,インスタンス変数(プロパティ)や,関数(メソッド)を扱うことができます。
今回も,カードゲームの作成を例にとって考えてみましょう。カードゲームを作成するために,個々のカードの情報を管理する「Card」クラス,そして,複数毎のカードを管理する「CardFolder」クラスを作成してみました(図2)。
図2: 二つのクラスファイル
それぞれのクラスは,以下のように定義しています。Cardクラスは「カードの情報を保持する」ことを目的に作成されたクラスです。「攻撃力」をあらわす「attackプロパティ」を持つシンプルなクラスです。
Cardクラス | ||
プロパティ | attack | 「攻撃力」をあらわすプロパティ |
Cardクラスのコードは次のようになります。
リスト1
class Card { var attack:Number; //コンストラクタ関数 function Card() { //0~9の間でランダムに攻撃力を決める attack = Math.random()*10 >> 0; }; }
CardFolderクラスは,「カードをまとめ,次のカードを引ける」ことを目的に作成されたクラスです。Cardクラスのインスタンスをまとめて管理する配列である「listプロパティ」と,現在のカードが何枚目なのかを管理する「indexプロパティ」を持っています。さらに,指定した枚数だけカードを追加する「addCardメソッド」と,次のカードを引く「getNextCardメソッド」を用意しています。
CardFolderクラス | ||
プロパティ | index | 現在のカードのインデックス番号 |
list | カードのリスト(配列) | |
メソッド | addCard(枚数) | 指定枚数カードを追加する |
getNextCard() | 「次のカード」を返す |
CardFolderクラスのコードは次の通りです。
リスト2
class CardFolder { var index:Number; //配列のインデックス管理用変数 var list:Array; //カードを管理する配列 //コンストラクタ関数 function CardFolder() { //カード情報の初期化 index = 0; list = new Array(); } //指定した枚数だけカードを追加する関数 function addCard(_n:Number):Void { for (var i:Number = 0; i<_n; i++) { //新しいカードを生成し,配列に追加 list.push(new Card()); } } //「次のカード」を引く関数 function getNextCard() :Card{ //indexの値に応じた位置のカードを返す return list[index++]; } }
この二つのクラスを使用し,とりあえず10枚のカードを準備して順番に「攻撃力」を表示するには,Flashドキュメント側に次のようにコードを記述します。
リスト3
//必要なクラスのインポート(*3) import Card; import CardFolder; //新規CardFolderを作成し,10枚カードを準備 var myCardFolder:CardFolder = new CardFolder(); myCardFolder.addCard(10); //10回カードを引く var card:Card; for(var i:Number = 1;i<11;i++){ //「次のカード」を取得 card = myCardFolder.getNextCard(); //カードのattackプロパティを表示 trace(i + "枚目の攻撃力は、" + card.attack); }
実行結果は次のようになります(図3)。
図3: 実行結果
Cardクラスのインスタンスのattackプロパティの値を使って,10枚のカードの値が取り出せましたね。
では,次のコードを見てください。「10回カードを引く」前に,次のようにindexプロパティの値を更新してしまったらどうなるでしょうか?
リスト4
//indexプロパティの値を更新 myCardFolder.index = 5; //10回カードを引く var card:Card; for(var i:Number = 1;i<11;i++){ //「次のカード」を取得 card = myCardFolder.getNextCard(); //カードのattackプロパティの値を表示 trace(i + "枚目の攻撃力は、" + card.attack); }
結果は,図4のようになります。カードが10枚しかないのに,indexプロパティの値を「5」からスタートしてしまったために,6枚目以降はカードが存在せず,エラーとなってしまっています。これでは困りますよね。
図4: 予期せぬ処理結果となる例
indexプロパティの値は,「次のカード」がどれかを判断するうえで必要な値なのですが,上記のようなコードで勝手に変更してほしくない値なのです。いままでの方法では,インスタンス変数はすべて参照も変更もできてしまうという,便利でもあるけれども,不便でもあるという弱点があるのです。
このようなケースでは,「カプセル化」の処理を行い,インスタンス変数へのアクセス制限を行うのが効果的です。
privateステートメントでアクセス制限
CardFolderクラスのindexプロパティの値へのアクセスを制限したい場合には,クラス定義ファイルにおいて,「privateステートメント」を使って宣言を行います。privateステートメントはvarステートメントとセットで記述します。
private var 変数名:データ型;
CardFolderクラスのindexプロパティの例で言うと,図5のように変更を行います。
図5: privateステートメントを追加する
今までの変数の宣言文の先頭に「private」を追加するだけですね。これで変数indexは,クラス定義内に記述した処理以外からはアクセスを制限することができます。
さて,ここでもう一度,リスト4のコードを実行するとどうでしょうか? 結果は,図6のようになります。indexプロパティがprivateステートメントによってアクセス制限されたため,Flashドキュメント上のコードからindexプロパティの値を変更しようとすると,アクセス制限違反のエラーとして認識してくれています。
図6: アクセス制限エラー
知らないうちにどこかでindexプロパティの値が,予期しないときに変更されてしまう,ということを防ぐことができますね。また,listプロパティも勝手にカードの追加や変更がされないように,privateステートメントを使って隠してしまいましょう。これでCardFolderクラスは,二つのメソッドだけを持つクラスとして使用できるようになりました。
このようにprivateステートメント使って宣言された変数を「プライベート変数」または「プライベートプロパティ」と呼びます。