神経衰弱アプリを作ってみる
7.カードのクラスを材料にオブジェクト指向を少しだけお勉強
さぁ!Mな気分でゾクゾクしながら待ってた皆様、オ・マ・タ
♥
ようやく「Cardクラス」の活躍にせまってみようか。
今回はチョット長いから、気合入れていこーぜーっ!
では前回のコードから、「Cardクラス」のオブジェクトの生成の部分に注目してみよう。
//カードの配列に、シャッフルした画像番号と画像をセットする・・・(D)
for (int i=0; i < ROW*COL; i++) {
card[i] = new Card(
cardArray.get(i),
images.getDrawable(cardArray.get(i))
);
}
ここで「Cardクラス」のオブジェクトを生成して、配列にセットしてるんだよね。
ちなみに、こんなふうにクラスから生成されるオブジェクトのことを「インスタンス」と呼ぶので覚えておこう。
さらにそれに従って、クラスからオブジェクトを生成することを「インスタンス化」って言うんだって。
そしてその記述方法は、例えば「MyClass」っていうクラスをインスタンス化して「myInstance」というインスタンスを作る場合、
MyClass myInstance;
と宣言して、
myInstance = new MyClass();
とするとか、あるいはこれらを一つにまとめて
MyClass myInstance = new MyClass();
って書くのが通常の場合のやり方。
この「new」って何だ?何が新しいんだ?って感じがするけれども、そのへんは深く考えない。
これは「new演算子」っていうもので、とりあえずインスタンス化の際はこれを付けるもんだって納得しておこう。
とにかくインスタンス化ってのは、
「クラス名」「インスタンス名」;
と宣言して
「インスタンス名」= new「クラス名」(あれば引数);
とするとか、それをひとまとめにして
「クラス名」「インスタンス名」= new「クラス名」(あれば引数);
のように書くのが普通ってこと。
ではここで「Cardクラス」に戻って、見直してみることにしよう。
今回は配列でインスタンスを生成してるんで、ちょっと特殊なんだけれども、よく見るとインスタンス化の書き方にほぼ一致してるのが分かる。
つまり、プログラムの最初のほうで配列の宣言をした
Card[] card = new Card[ROW*COL];
の部分と、上記のforループの中の
card[i] = new Card(cardArray.get(i),images.getDrawable(cardArray.get(i))
の部分を合わせたのが、
「クラス名」「インスタンス名」;
「インスタンス名」= new「クラス名」(あれば引数);
の記法にだいたい一致してるでしょ。
実のところ、配列の宣言をしている
Card[] card = new Card[ROW*COL];
っていうのは
Card[] card;
card = new Card[ROW*COL];
っていうのを1つにまとめたもので、これはこれで配列をインスタンス化してるんだけれども、ややこしくなるのでその辺は今は考えないことにしよう。
忘れるのじゃ、すべて忘れるのじゃ・・・ウン忘れた。
という感じで、クラスのインスタンス化のための記述方法については何となく飲み込んでもらえたかな?ナニ飲み込めない?ここはとりあえず無理してでもグビリと飲み込んでくれよ、とりあえずグビリとね。
では次に、「コンストラクタ」について勉強してみよう。
「コンストラクタ」って言葉は以前出てきて、そのときは保留にしてたものだけれども覚えてるかな。
とにかくまずは、前述のインスタンス化のこの記述に注目してもらいたい。
「インスタンス名」= new「クラス名」(あれば引数);
この「new」の後の
「クラス名」(あれば引数)
の部分。
これが実は1つのメソッドを表していて、それがまさに「コンストラクタ」なんだね。
つまりクラス名と同じ名前のメソッドが、「コンストラクタ」ってこと。
例えば以下のような「MyClass」というクラスがあったとしよう。
class MyClass {
int x;
MyClass(int x) { //・・・(※)
this.x = x;
}
}
このとき、(※)の記述が「コンストラクタ」を定義している部分だ。
そしてこの「MyClass」をインスタンス化するとき、
MyClass myInstance = new MyClass(100);
とすると、上記の(※)の部分が呼び出されるんだね。
その結果どうなるかというと、
MyClass(int x)
の「x」に「100」が代入されることになる。
そうして「this.x」に「100」が代入されて、結果として「myInstance」の中の「x」が「100」という値を持つことになるってわけ。
ちなみに「this」っていうのはクラス(インスタンス)自身を表していて、
this.x = x;
っていうのは、クラス(インスタンス)の「x」に、コンストラクタの引数の「x」の値を代入してるってこと。
「this.x」と「x」とは、別物なので要注意。
というわけで簡単にまとめると、「コンストラクタ」っていうのは、
・クラスと同じ名前を持つメソッド
・クラスをインスタンス化するときに呼ばれるメソッド
という2点がメッチャ重要なので、今のところはコレだけ脳に刻んでおこう。
では脳に「コンストラクタ」の文字がキリキリと刻まれたと思うので、次に「Cardクラス」に戻ってコンストラクタを見てみよう。
覚えてるかな、この「Cardクラス」の定義の(C)の部分がそれだね。
class Card {
private int imgNo; //画像番号・・・(A)
private Drawable cardImg; //画像 ・・・(B)
Card(int imgNo, Drawable cardImg) { //・・・(C)
this.imgNo = imgNo;
this.cardImg = cardImg;
}
int getImgNo() { //・・・(D)
return imgNo;
}
Drawable getCardImg() { //・・・(E)
return cardImg;
}
}
このコンストラクタで何をやってるかっていうと、引数で渡された「画像番号」と「画像」をフィールドにセットしてるんだね。
つまりこの「Cardクラス」はインスタンス化される際に、「画像番号」と「画像」がセットされるってわけだ。
それだけの話なんだけれども、ココがけっこう大事。
なぜなら、この「Cardクラス」はそれ以外に「画像番号」と「画像」をセットする方法が無いから。
エッ、他に方法が無いの?と思った君、それでは「Cardクラス」の「imgNo(画像番号)」と「cardImg(画像)」のフィールド宣言をしている部分をよく見てみよう。
両方とも「private」が先頭についてることに気がついたでしょ?これが、ポイントなわけよ。
これは「アクセス修飾子」と呼ばれるものの一つで、フィールドやメソッドなどに対するアクセスを制限するために付けられるものなんだね。
例えばフィールドに対して、他のクラスやパッケージなどから読み書きをできるようにしたり、あるいはできないようにしたりするために付けるってわけ。
ちなみにココで使われている「private」ってのは一番強い制限で、これが指定されているフィールドはクラス内だけでしかアクセスすることができない。
だから、他のクラスからは読み書きができないフィールドってことなんだね。

つまりアレだ、「プライベートには立ち入らないでくださいっ!」的な?「女のコのプライベートな部分は感じやすいから触っちゃダメ!」的な?
あー・・・何か話がエロい感じになってきたな、違うっ違うぞ!そーじゃなくってーいゃこんなことで興奮しないから!思春期の中学生じゃないんだから。
・・・話を戻して。
ではここで「private」で宣言してるために「Cardクラス」の外からは読み書きできなくなった「imgNo(画像番号)」と「cardImg(画像)」にはどうやってアクセスしたらいいのか?
そこでまずは最初のインスタンス化のときに一度だけ書き込みができるように、「コンストラクタ」を定義したってわけだね。
これでとにかく書き込みについては、できるようになったと。
では読み込みはどうするかというと、その下の(D)と(E)のメソッドがその対応手段。
つまりこの読込み専用のメソッドを使うことによって、「Card」のインスタンスの「imgNo(画像番号)」と「cardImg(画像)」の値を取得できるようにしたんだね。
ちなみに、これらのようにフィールドの値を読み込むメソッドのことを「ゲッター」と呼び、今回は無いけれども反対に値を書き込むメソッドを「セッター」と呼んだりする。
そして両方を合わせて「アクセサ」と呼んだりするので、ご参考まで。
で・・・結局何でそんな「アクセス修飾子」や「アクセサ」とかを使ったり面倒くさいことするのかって言うと、それはクラスを「カプセル化」するためなんだね。
あーまた何だか変な言葉が出てきたなー何だよ「カプセル化」って?とか思わないで、できるだけ短めにするからもう少し我慢して話を聞いてよ。
実はこの「カプセル化」ってのがJavaはもちろんのこといわゆる「オブジェクト思考」と呼ばれるもののキモだったりするもんで、深入りするとトッテモ謎な世界に迷い込んでしまうんだよね。
だから、この場ではごく簡単な点だけにしぼって頭に入れておくことにしよう。
まず「アクセス修飾子」や「アクセサ」を使ってインスタンスの内部への干渉を制御すれば、つまるところインスタンスの使い方を制御することができるようになる。
つまり、ウッカリ間違った方法でインスタンスを生成したり使ったりということを防ぐことができるようになるんだね。
以前、クラスからインスタンスを生成することを「タイ焼きの焼き型」から「タイ焼き」を作ることに例えたことがあったけれども覚えてるかな。
その例で言うならば、「カプセル化」することによって「タイ焼きの焼き型」やそれからインスタンス化された「タイ焼き」に魔法をかけることができるようになるんだね。
例えばその焼き型に、「タイ焼きに自動的に餡子を詰める」っていう魔法をかけたとするじゃない。
これはつまり、「コンストラクタ」にデフォルトで「餡子を詰める」っていう処理を追加したってことだ。
すると焼き型からタイ焼きをインスタンス化した際に、何もしなくても自動的に餡子が詰まったタイ焼きが出来上がることになる。
結果として、その焼き型をサザエさんばりにそそっかしいオカンが使ったときでも、ウッカリ餡子を詰め忘れて餡子抜きの美味しくないタイ焼きを食べさせられるなんていう悲劇を防ぐことができるってわけ。
あるいは、「タイ焼きの中に餡子以外のものが入れられたら、タイ焼きから大音量のブザーが鳴って警告を発する」なんていう魔法をかけたとしよう。
これはつまり「セッター」に入力条件を付けて、間違った入力に対してはエラーを出力して警告するようにしたってこと。
すると、超辛いモノ好きのオトンが、焼きあがったタイ焼きの中に刻んだハバネロを入れようとしたら、「ブーーーッッ!!」っていう凄まじい警告音で怒られるなんてことになるわけ。
結果として、オトン特製の超激辛タイ焼きをウッカリ食べさせられて口の中が大火事になるなんていう惨事を防ぐことができるんだね。
さらに言うならば、オカンやオトンはこの魔法については一切その仕組みを知る必要が無いってこと。
とにかく水溶き小麦粉を入れて焼けば、美味しいタイ焼きができるってことだけ分かってれば良いんだね。
何か話が非現実的すぎて逆に意味不明になりそうだけれども、要するに「カプセル化」によってクラスやインスタンスの使い方のウッカリミスを防ぐことができるってこと。
これを逆に使うほうから考えれば、ウッカリミスを気にせずに使えるので便利ってわけだ。
この「使う人が便利」っていうのはクラスをカプセル化する一番の目的なので、ココとっても大事。
もちろん「使う人」の中には「作った自分」も含まれるのが普通なわけで、自分のためにも面倒くさいとか思わずクラスの作成においては「カプセル化」を頭に入れて行うのが吉だ。
しつこいようだけれども念のため、さらに「カプセル化」をもっと抽象的にも考えてみよう。
「カプセル化」っていうのは、その名のとおり処理をカプセルで包んでしまうわけだね。
カプセルで包んでしまうっていうのはどういうことかというと、「中身が分からないようにする」と言うよりも「中身が分からなくてもいいようにする」ということだ。
つまり中で何をやっているのか分からなくても問題なく使うことができるのが、「カプセル化」するっていうことになる。
要するに使う人は、インスタンス化の方法やメソッドの入出力の取り決めさえ分かっていれば良いんだね。
仮にカプセルの中身で何か変更があったとしても、その決め事さえ変わらなければ利用者に影響は無い。

しかも、ウッカリ間違って取り決めに反した使い方をする恐れも無い。
「カプセル化」されたクラスは、誰でも安心して利用することができる。
詰まるところ「カプセル化」のすべては、そのクラスを使う「誰か」のため。
そして、そのクラスを使う「自分自身」のためってこと。
ただブッチャケた話すると、そういうメリットは複数の人員で開発をするときや大きなシステムを作るときほど効果が大きく発揮されるんだよね。
この「神経衰弱アプリ」程度の小さなプログラムや個人で使うツールなんかだと、そのメリットを感じる機会はあまり多くないことは確かだ。
でもまー勉強だと思って今回の内容を雰囲気だけでもつかんでおけば、後々のアプリ開発の際にきっと役にたつからね・・・・多分きっと。
とにかく「カプセル化」とかの言葉だけ追いかけるといろんな解釈があって混乱すること請け合いだから、ここではそういう言葉や考え方があるんだなぐらいに理解してもらえればとりあえずオッケーだ。
というわけでチョット長い勉強だったけれども、今回はひとまず終了。
次回は、いよいよ並べたカードを実際にAVDで確認してみるぞ。