前回に引き続き、メイドさんにプログラミングを教えるためのシリーズです。
前回「オブジェクトの多態性ができると便利です」と説明しました。今回は、なぜ便利なのか分かるように、プログラミングでよく使われるテクニックを6つ紹介したいと思います。
これらを知ることが出来ればオブジェクト指向のメリットを理解出来るだけでなく、実践でプログラミングをしているプログラマーと、かなりコアな会話ができるようになりますよ!
その1: 関数オブジェクト
プログラムをしていると「何か起きた時に、この関数を実行して欲しい」という時が良くあります。このような、ある条件の時に実行してもらう関数のことを「コールバック関数」と呼びます。
Javaはメソッドを変数や関数のパラメータに代入することができませんので、こういう時、コールバックして欲しい処理を書いたメソッドを持つオブジェクトを作って、それを渡します。
このようなオブジェクトのことを関数オブジェクトと呼びます。
例えば、架空のクラスですが、WebClientというWebサーバーに通信をして、通信が成功した時に行う処理を指定できるようになっているとします。WebClientResponseCallBackというインターフェース
webClient.connect(new WebClientResponseCallBack() {
@Override
public void onSucceeded(int status, String result) {
System.out.println("成功しました");
System.out.println(status);
System.out.println(result);
}
@Override
public void onFailed(String result) {
System.out.println("失敗しました");
System.out.println(status);
System.out.println(result);
}
});
これは架空のコードですが、このようなスタイルのライブラリはたくさんありますので、慣れておきましょう。
※@Override
という記号が初めて登場しました。これはアノテーションと言うもので、プログラムにつける印です。様々な場面で使用しますが、@Override
アノテーションは、継承してメソッドを上書きしていることを示すものです。必須ではないため前回は省略しましたが、付けないとコンパイラ警告が出てしまいますので、今回からは付けることとします。
その2: Template Method パターン
関数オブジェクトと似たような用途として、クラス継承を活用して、処理を記述する方法もあります。
これも、架空のクラスですが、例えば、画面があって、OKボタンを押した時に何が起こるかはプログラミングできるようになっているライブラリが合ったとします。
SampleScreen s = new SampleScreen() {
public onClickOkButton() {
System.out.println("ボタンが押された!!");
}
};
s.show();
このような、クラス継承を利用して、一部分のメソッドだけサブクラスに委ねるテクニックを、Template Method パターンと呼びます。
その3: ラッパーオブジェクトパターン
例えば、以下のコードは今まで「おまじないです」と言われていたと思います。
BufferedReader reader = new BufferedReader(new FileReader("data.dat"));
これは、FileReader
というクラスをBufferedReader
クラスが包みこんでいます。BufferedReaderのような、内部にオブジェクトを包み込むオブジェクトのことを「ラッパー(Wrapper)オブジェクト」と呼びます。楽器のラッパじゃないですよ。●ランラップのラップです!
FileReaderは、ファイルを読み込むことができますが、バッファリングの機能はありません。一方、BufferedReaderはバッファリングの機能はありますが、自らファイルを読み込む機能はありません。
そこで、BufferedReaderでFileReaderを包み込むことによって、バッファリングしながらファイルを読み込むことを実現しています。
どんなにオブジェクトを包み込んでいても、外側から見ると内側は隠蔽されています。そして、インターフェースさえ一致していれば、内側に入れるものを切り替えてもプログラムが動きます。
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in);
どんなときに、ラッパーオブジェクトを作るのでしょうか?主に次のようなとき作られます。
- 内側のクラスのインターフェースを変換して、別のインターフェースとしてAPIに渡す
- 内側のクラスの動作に付加機能を付け加える
このように、ラッパーオブジェクトはプログラムをとても柔軟にするテクニックです。
その4: Factory
クラスからインスタンスを作るとき、
new クラス名();
と書くと説明してきました。しかし、オブジェクト指向の世界で、new
はわりと嫌われ者です。何故かと言うと、これまでのテクニックの真髄はクラスを切り替えることにあります。
SoundsMaker s = new Cat();
s.makeSounds(); // ニャー
SoundsMaker s = new Dog();
s.makeSounds(); // わん
せっかくSoundsMaker
インターフェースを抽出しているのに、プログラムの中に new
があるため、動作が決定してしまいるのが良くないとされます。
そこで、newをなるべく隠蔽しようと言うテクニックとしてFactoryパターンというものがあります。
例えば、newを直接書かず、メソッドにしてしまうアイデア
public static SoundsMaker getSoundsMaker(String name) {
if ("tama".equals(name)) {
return new Cat();
}
if ("pochi".equals(name)) {
return new Dog();
}
return null;
}
SoundsMaker s = getSoundsMaker("tama");
s.makeSounds(); // ニャー
SoundsMaker s = getSoundsMaker("pochi");
s.makeSounds(); // ワン
getSoundsMakerのプログラムを見なかったとしたら、呼び出し元は何のインスタンスが作られるか分かりません。しかし、そんなことを知らなくても、SoundsMakerインターフェースであることは保証されているので、その範囲でできることは安心して使うことができます。
その5: MVCモデル
これは考え方であって、コード例はありません。ざっと覚えておいてください。
一般的に比較的大きなプログラムを書くときは、次のように分割すると良いと言われています。
役割 | |
---|---|
モデル | ロジックを担当 |
ビュー | 表示を担当 |
コントローラ | ユーザーの操作に応答し、モデル・ビューを操作する |
例えばAndroidで言えば、Activityクラスはコントローラです。もしActivityに全部のプログラムを書いてしまうと、だんだん巨大になってしまいますので、必要に応じて、モデルを切り出すと全体の見通しが良くなります。
ビューは、レイアウトの部分で、同じActivityに異なるレイアウトファイルを与えても動く点でAndroidはビューとコントローラについて抽象化されていると言えます。
その6: モックオブジェクト
最後に、モックオブジェクトについて説明します。
「モック」:モックアップ(mock up)とは、店頭にある展示用模型のことです。本物ではありませんが、本物のような見かけをしています。
プログラムを作っていると、「Aというオブジェクトを使用するにはBというオブジェクトが必要」「Bというオブジェクトの初期化にはCという情報が必要」「CはAndroid実機じゃないと取得できない」などのように、芋づる式に、あらゆるオブジェクトがからみ合ってくることがあります。
しまいには、非常に大きなオバケのような存在になってしまい、一箇所変更すると、どこに影響するのか全然わからなくなることがあります。
この問題を回避するために、本物ではない、仮のオブジェクトを作って動作確認を行うというテクニックが行われます。
そのためのオブジェクトを「モック」あるいは「スタブ」と呼びます。スタブとは「代用品」という意味です。切り株が原義のようです。
インターフェースを使ってプログラミングがされている場合は、比較的カンタンにモックを作ることが出来ます。
interface SoundsMaker {
void makeSounds();
}
お馴染みのこのインターフェース。実は今アナタはゲームを作っていて、そのゲームでは音を扱っているとします。そこではSoundsMakerインターフェースを継承したオブジェクトがmakeSoundsとやると実際にスピーカーから音が鳴るのです。
class Bomb implements SoundsMaker {
void makeSounds() {
// 複雑なプログラム
// 結果的にスピーカーから音が鳴る
// 「ドカーン!!」
}
}
しかし、Bombクラスを動かすためには、どうやら専用のハードウェアがないといけないそうです。
でも、音を鳴らす以外の部分は、どうやら普通のパソコンでも動かせるみたいです。
そんな時は、モックオブジェクトの出番です。
class BombMock implements SoundsMaker {
void makeSounds() {
System.out.println("ドカーン!");
}
}
Bombクラスの代わりにBombMockクラスを使用するように切り替えれば、特殊なハードウェアがなくても、プログラムが動くようになります。その代わり、音が鳴るタイミングで音はならず、代わりに
ドカーン!
という文字が出力されます。
本物のオブジェクトとモックの切り替えは、オブジェクトインスタンスをnewで生成するところで行わなければなりません。そのためには「その4」で説明した、「ファクトリー」を使用する検討が必要になってきます。
まとめ
今回説明したテクニックを使う、プログラムからどんどん野暮ったさが消えて洗練されて来ていると思いませんか?
しかし、洗練し過ぎも問題です。
例えば、Factoryの説明で作った次のコードは
SoundsMaker s = getSoundsMaker()
s.makeSounds();
元の
Cat tama = new Cat();
tama.mew();
だった時のほうが、垢抜けない可愛さがあります。
洗練されたプログラムというのは、高貴でお近づきにくい存在なのです。より多くのことを知らないと読めないし、知っていてもどんな動きをするのか「実行するまで分からない」ときます。
どこまで洗練すれば良いかは、作っているプログラム次第です。一般的に大きなプログラムは、より洗練されていることが求められますが、個人で作るアプリでは、あどけない感じで作ったほうが正解だったりします。
この感覚は、実際にアプリを作って行くことで培われていきますので、実践で身につけて行ってください。