オブジェクト指向でテレビを作る
オブジェクト指向によるプログラミングを始める前に、私のリビングにある私のテレビについて考えてみましょう。私がそのテレビに期待していることはなんでしょうか?それは、以下のようなことでしょう。
私が私の家のテレビに望んでいることがほとんどそのまま、Javaのクラスにも当てはまります。Javaのクラスはテレビや日用品のようなものなのです。Javaのクラスは、次の4つの条件を満たしている必要があります。
では、実際にオブジェクト指向でテレビを設計していくことにしましょう。
ではまず、一般的な「テレビ」を単純化し、それに必要な操作を考えてみましょう。それは、以下のようなものです。
次に、テレビに必要なデータ(情報)は何かを考えます。
このTVクラスをJavaで表現すると、以下のようになります。
class TV { int m_nChannel;// テレビのチャンネル int m_nVolume; // テレビのボリューム // チャンネルの変更 void Channel (int nNewChannel) { m_nChannel = nNewChannel; } // ボリュームの変更 void Volume (int nNewVolume) { m_nVolume = nNewVolume; } } |
このテレビを実際に使う「私」の行動は、次のようになります。
public static void main(String args[]) { TV myTV = new TV(); // これが私のテレビです! myTV.Channel(7); // 私のテレビのチャンネルを7に合わせる myTV.Volume (10); // 私のテレビのボリュームを10に合わせる } |
・・・いかがでしょうか?これでTVの設計は完成したように見えます。TVクラスはChannelやVolumeといったテレビに必要な操作を提供しています。TVクラスは概念化されていて、私のTVは私のTVだと認識できます。しかし、これでは十分ではないのです。
もし、私がテレビのチャンネルを99に合わせたとしましょう。今作ったChannnel関数(メソッドという)は、99のような不正な入力(今私のテレビのチャンネル数は12しかありません!)をチェックしません。99のようなチャンネルを現在のTVクラスは処理できないので、きっとこのTVは炎上して壊れるでしょう。前にも言ったように、テレビのユーザは99のようなチャンネルを入力したことによってテレビが炎上するようなことを想定していません。多分このテレビのユーザはPL法かなんかでTVクラスの製造者を訴えるでしょう。それではまずいのです。
では、99のような不正なチャンネル入力をチェックして拒否するように、Channelメソッドを変更してみましょう。
Java言語で記述される実際のTVクラスは、次のようになります。
class TV { int m_nChannel;// 現在のチャンネル int m_nVolume; // テレビのボリューム int MIN_CHANNEL = 1; // 最少チャンネル int MAX_CHANNEL = 12; // 最大チャンネル // チャンネルの変更 void Channel (int nNewChannel) { // Channnelメソッドはm_nMAX_CHANNEL以上のチャンネル入力を拒否します。 if (nNewChannel > MIN_CHANNEL && nNewChannel < MAX_CHANNEL) { m_nChannel = nNewChannel; } } // ボリュームの変更 void Volume (int nNewVolume) { m_nVolume = nNewVolume; } } |
これを利用するmainメソッドは、次のようになります。
public static void main(String args[]) { TV myTV = new TV(); // これが私のテレビです! myTV.Channel(7); // 私のテレビのチャンネルを7に合わせる myTV.Volume (10); // 私のテレビのボリュームを10に合わせる myTV.Channnel(99) // この入力は無視されます } |
これで完成でしょうか?実はそうではないのです。今のままでは、クラスのデータメンバにどこからでもアクセスできてしまうのにお気づきでしょうか?たとえば、最大チャンネル数や最少チャンネル数が、テレビのユーザから変更できてしまいます。それは、次のようなコードで表わされます。
public static void main(String args[]) { TV myTV = new TV(); // これが私のテレビです! myTV.Channel(7); // 私のテレビのチャンネルを7に合わせる myTV.Volume (10); // 私のテレビのボリュームを10に合わせる myTV.Channnel(99) // この入力は無視されます myTV.m_nChannel = 99; // 現在のチャンネルを99に設定!(入力チェックは行われない) myTV.MAX_CHANNEL = 99; // 最大チャンネル数を99に設定! myTV.Channel(99) // この入力は通ってしまうが、TVは炎上する } |
最大チャンネル数や現在のチャンネルにそのままアクセスできるようでは、せっかくの入力チェックも意味がありません。Channel メソッドを使わなくても、現在のチャンネルにアクセスできてしまうのですから!また、最大チャンネル数をユーザから変更されてしまっては、たとえChannel メソッドで入力チェックを行っても、それは意味がありません。では、どうすればいいのでしょうか?それは、アクセス制御という方法を使い、このデータメンバ(最大チャンネル数や現在のチャンネル)をユーザから隠して、Channelメソッドのみでチャンネルにアクセスできるようにしなくてはいけないのです。
アクセス制御を施したTVクラスは、次のようになります。
public class TV { private int m_nChannel;// 現在のチャンネル private int m_nVolume; final int MIN_CHANNEL = 1; // 最少チャンネル final int MAX_CHANNEL = 12; // 最大チャンネル // チャンネルの変更 public void Channel (int nNewChannel) { // Channnelメソッドはm_nMAX_CHANNEL以上のチャンネル入力を拒否します。 if (nNewChannel > MIN_CHANNEL && nNewChannel < MAX_CHANNEL) { m_nChannel = nNewChannel; } } // ボリュームの変更 public void Volume (int nNewVolume) { m_nVolume = nNewVolume; } } |
たったこれだけで、不正なチャンネル変更や最大チャンネル数への不正アクセスを防ぐことができます。例えば、次のようなコードは、エラーになります。コンパイルも出来ません。
public static void main(String args[]) { TV myTV = new TV(); // これが私のテレビです! myTV.Channel(7); // 私のテレビのチャンネルを7に合わせる myTV.Volume (10); // 私のテレビのボリュームを10に合わせる myTV.Channnel(99) // この入力は無視されます myTV.m_nChannel = 99; // エラー (私からは見えない) myTV.MAX_CHANNEL = 99; // finalで宣言されているため、データの設定は出来ない myTV.Channel(99) // 当然これも実行時に無視される } |
これで、強固なTVクラスが出来ました。
さて、もしあなたが、リモコンでチャンネルを変えた時に、同時に音量も設定して欲しいと願ったとしましょう。それは、このテレビクラスのChannelメソッドを少し拡張することによって実現できることがわかります。さあ、どうやって実現するのでしょうか?新しいChannel_Volumeメソッドかなんかを作成して、その呼び出し時にChannelメソッドとVolumeメソッドを同時に設定するのでしょうか?
public void Channel_Volume (int nNewChannel, int nNewVolume) { m_nChannel = nNewChannel; m_nVolume = nNewVolume; } |
しかし、このアプローチは、TVのユーザが知らなくてはならないメソッドを増やしてしまいます。リモコンで言うなら、新しいボタンが一つ増えるようなものです。これでは混乱の元にもなりかねません。では、どうするのでしょうか?
オブジェクト指向のアプローチでは、メソッドのオーバーロードという方法を用いて、以下のようにすることが出来ます。
public void Channel (int nNewChannel, int nNewVolume) { m_nChannel = nNewChannel; m_nVolume = nNewVolume; } |
・・・どうでしょうか?「Channelメソッドはすでに出てきたからもう使えないじゃないか」という方もいらっしゃるかもしれません。たしかにこのアプローチは今までの手続き型のプログラムでは、不正です。しかし、Javaではこれが可能なのです。
では、以前出てきたChannelメソッドと今回出てきたChannelメソッドをどのようにして区別するのでしょうか?それは、引数です。Javaでは、Channel()メソッドとChannel(int nNewChannel, int nNewVolume)メソッドは全く別の物として解釈されます。TVクラスのユーザは、同じようにChannelメソッドを呼び出しますが、その時に引数を加えることによって、同じ名前の関数でも違う機能を持たせることができるのです。それは、以下のようになります。
public static void main(String args[]) { TV myTV = new TV(); // これが私のテレビです! myTV.Channel(7); // 私のテレビのチャンネルを7に合わせる myTV.Volume (10); // 私のテレビのボリュームを10に合わせる myTV.Channel (12, 7); // チャンネルを12に変更し、ボリュームを7に合わせる } |
ユーザはこの時点で2つのメソッドしか知らなくてよいのです。Channelメソッドにもっと引数を増やしたメソッドを定義した場合、これが手続き型のアプローチであれば、ユーザが知らなくてはならないメソッド名はもっと増えてしまいます。
これが、メソッドのオーバーロードと呼ばれる方法です。これはオブジェクト指向のアプローチです。
次の章では、さらにこのTVクラスを拡張させていきます。