第三の基本は抽象化です。
抽象というのは具体の反対ですが、一見難しく感じますが、難しいことではありません。
例えば、小泉純一郎、松井秀喜、浜崎あゆみ、と人の名前を挙げていくと具体的ですがが、それらを一括りに日本人とすると抽象的になります。さらに人間とするとさらに抽象化が進みます。抽象化の利点は、その「人間」と表現することで、小泉純一郎も松井秀喜も、だれでも含めることができることです。「人間には、基本的人権が与えられる」と表現することで、いちいち、「小泉純一郎には基本的人権が与えられる」「松井秀喜には...」と全部列挙していかなくて済みます。
さらに抽象化が進むと、「生物」という概念が出てきます。生物には、人間も動物も植物も含めることができます。抽象化は階層をなしています。上位に行けば行くほど包括的で、下位に行けば行くほど限定的になります。
抽象的というのはしばしば話がよくわからないというときに使われます。「君の言っていることは抽象的だ。もっと具体的に話してくれ」といったように。一般的に難解な本を読むと、そこは抽象的な表現の嵐です。例えばこんな具合です。「人間は本能によって動かされている」。人間も、本能も、動くも抽象的で、各言葉にはいろいろなものが含まれています。これだけでは、わからないので具体的に、歴史的な人物を取り上げ、どのような動機で何をしたのかを列挙するのです。そして最後に、もう一度最初の文を繰り返して、結論付けるのです。何かの説明をする場合、たいてい抽象的表現と具体的表現がセットになっています。抽象的だけだとピンと来ませんし、また具体的だけだと結局何が言いたいのかよくわからないのです。つまり抽象とは、まとめであり、また本質を意味しています。
また抽象化は、新しい言葉を作るときにも使われます。例えば、コンピューターの演算部と制御部をまとめてCPUと言います。CPUという言葉がなければ、長々と説明したり、構成要素をすべて言わなければならないのですが、CPUを使うことによって、それらを省略して一言で済むようになるのです。物事が複雑になればなるほど専門用語が現れます。長い表現を繰り返すのを避けるために、それを表す言葉を作って、説明を簡略化するのです。
プログラミングの世界でも抽象化は多く用いられます。いろいろな値を入れるための変数や、複数の変数をまとめた構造体や、処理をまとめたマクロや関数が使われます。それらはすべてプログラムの中で、名前を使って指し示します。変数は値を抽象化します。関数は処理を抽象化し、関数名だけあればよくなります。これらのことは後でもう少し詳しく説明します。
抽象化は、数学の分野で頻繁に使われます。変数の発見は、数学を大きく進歩させました。具体的な数を使わずに簡潔に表現できてしまうのです。変数のないところでは、
単価 消費税 合計 100円 5円 105円 120円 6円 126円 140円 7円 147円
と一つ一つ書いていかなければならないところを、
単価 消費税 合計 x x * 0.05 x * 1.05
と一行ですべてを表現できるところです。
そして、変数の使用はプログラムの基本です。変数を使わないプログラムは、決まった結果しか出せません。
System.out.println(10 * 10 * 3.14);
これは10cmの円の面積を求めるプログラムで、別の値を計算したければ、もう一度プログラムを作り直さなければなりません。このようなソースは、ハードコーディングしていると言われます。ここで変数を使って引数で半径を渡せば、プログラムを作り直す必要はなくなります。
void showArea(int x) { System.out.println(x * x * 3.14); }
つまり、変数を使うことで、可変性を手に入れることができるのです。抽象度を高めれば、汎用性が増すのです。
System.out.println(radius * radius * PAI);
と書き換えると、意味がはっきりします。ただし、それは適切な変数名を使った場合に限ります。それゆえ、適切な変数名を使うことは、ソースの可読性を増す上で重要なことなのです。また、例えば、エラーコードに数字を使う場合も、ソース上では変数を使います。
final int INPUT_ERROR = 0; final int SYSTEM_ERROR = 1; return SYSTEM_ERROR;
このような形で具体的な数を出さないようにします。
また、先のエラーコードのような場合、実際の値を隠蔽することができます。上記の場合、どの値が使われているかをプログラムの中で表示してしまえば、わかってしまいますが、ドキュメント上では隠蔽することができます。また隠蔽という点では、値や変数を関数によって隠蔽することができます。
class A { private int a; public setA(int a) { ...; } public getA() { ...; } }
この例では、外からではsetA(),getA()メソッドしか見えず、また代入にsetA()メソッド、参照にgetA()メソッドを使用していますが、クラスA内部でどのようにデータを保持しているのかはわかりません。setA()やgetA()で値を変換しているかもしれませんし、内部にaという変数を持っていないかもしれません。これが抽象データ型といわれるもので、実際の値をメソッドによって抽象化しているのです。
また、変数を使うことで、長い文字を短縮することもできます。
String MSG_WAIT = "ただいま処理中です。もうしばらくお待ちください。"; int INPUT_ERROR = Constants.SYSTEM_MANAGEMENT_ERROR_BY_INPUT;
これは定数を使用する際にしばしば用いられます。変数の中で値が変わらないものを定数と呼びます。変数と違い、プログラムの途中で値を変えることはできません。
また、変数および型を定義することで、複雑なものを簡単に、複数のものをひとつとして扱うことができます。つまり、構造体、複合体、クラスのことです。
class Person { int id; String name; String address; String tel; int age; } Person p = new Person(); p.name = "山田太郎"; p.address = "東京都港区赤坂1-1-1"; ...
このように、バラバラな値を一つとして扱うことができるようになります。すべての属性について言及しなくても、そのインスタンスに対する参照一つで識別することができます。
以上のことから、変数を使用することによって、具体的な数や文字を置き換えることができ、また変数にあらゆる値を当てることができ、様々なケースを一つにまとめて表現できるのです。
変数、参照、ポインタ、ラベル、マクロ、エイリアス、名前、別名、リンク、識別子、いろいろと呼び名は違っていますがすべて抽象化のことをいっています。
変数は何かを指し示します。指すものと指されるものがあります。指されるものは変数に格納される値です。あるいは、値とは変数が指し示しているものということもできます。
指す者は、仲介役をしているということもできます。その仲介役を使うことによって、先に述べたことの繰り返しになりますが、
1. 難しいものを簡単なインターフェースとして提供できる(インターフェース)
2. 指されるものを隠すことができる(隠蔽)
3. 指し示されるものを交換することができる(可変性)
という利点が得られます。
1の「難しいものを簡単なインターフェースとして提供する」というのは、指されるものを人間にわかりやすい言葉で置き換えることです。例えば変数というのは単なるメモリ上の1〜数アドレスに格納される値を入れる箱のことで、変数を使わなくても、F13Bhなどといったアドレスで直接示すようにすることもできます。しかし、これではわかりにくいし、アドレスが固定されて柔軟性に欠けるので、それを抽象化して、変数名をつけるのです。このような抽象化は、具体的なF13Bhなどのアドレスよりも、人間にとってわかりやすいものです。
さらに、仲介役を使うことで、
4. 容易に修正できる
という利点もあります。これは、変数に設定する値を、外出しにして、設定ファイルや引数で指定するようにすることで、コンパイルすることなしに、アプリケーションの動作を変化させることができるのです。
この「コンパイルすることなしに」というのは重要です。コンパイルというのは、時間がかかる操作で、実行環境よりもコンパイル環境の方が必要なものが多いものです。またソースも必要になります。多くの項目を設定ファイルで制御できるようにすることで、ユーザーにソースを渡す必要もなく、また面倒なコンパイル環境を整え、コンパイル時間をかけさせる必要もなくなるのです。(かといってあまりにも柔軟すぎる設定ファイルも問題ありますが)
また、仲介役を一箇所に集めることで、
5. 修正箇所を一箇所に集中することができる
という利点も出てきます。これは、定数クラスを作ってすべての可変の情報を集めたり、設定ファイルにすべての可変情報を集めたりすることで実現します。
また、繰り返しにもなりますが、
6. 使う側と指し示される側との結合度を弱める
ということも言えます。もし仲介役がいないと、直接結合することになり、密接に関係し、使う側は指し示される側に直に依存することになります。そうすると修正や交換は容易ではなくなります。
最初に取り上げたように、人間の使う言葉、それ自体が変数です。定数の場合もあれば、変数の場合もあります。例えば犬と言った場合には、様々な種類や様々な数いる犬の中のどれかを指し示すことができます。例えば日本と言った場合には、指し示すものは特定されています。こういうものは固有名詞と言われます。また、何でも指すことができるものがあります。それが代名詞と言われるもので、「それ」「これ」「あれ」などがそうです。
例えば、あなたが友人の側にある本を手に取りたくて「それ取ってちょうだい」と頼むとしましょう。このとき、あなたは「それ」という代名詞に本を代入し、友人はあなたの「それ」を聞いて、「それ」が指し示すものを理解し、本をあなたに手渡すことになります。
こうして見ると、世の中は抽象化で溢れ返っているように見えます。抽象化は、言葉だけではありません。何を隠そう、お金も抽象化です。変数とは少し違いますが、お金は様々な価値あるものを抽象化したものです。
人間の言葉自体が抽象的なものだとすると、プログラミング言語も実は抽象的なものなのです。コンピューターに理解できるのはマシン語だけです。それを人間がわかるように抽象化したのが、アセンブラであり、さらにもっと抽象化したのが、CやJavaなどの高級言語です。
抽象化というと、何か難しく感じるかもしれませんが、実際抽象化によって人間にとって難しいことが簡単になることもあるのです。今述べた高級言語の例もそうですし、またGUIアプリケーションなどで、保存されているデータの内容をわかりやすく表示するのも抽象化の一例です。
変数は値を抽象化し、クラスはインスタンスを抽象化します。抽象クラスは具象クラスを抽象化します。インターフェースは、実装を抽象化します。
インターフェースは顔であり、窓口です。中身がどうなっているか気にする必要がありませんし、見ることもできません。APIを使いたい人にとって、知っていればいいのは、何という名前のメソッドをどういう引数をつけて渡せばいいのか、そしてどういう結果が得られるのか、というただそれだけです。その実装は非常に長いソースコードかもしれません。しかし、使う側はわずか1行を書きさえすればいいのです。これが抽象化の力です。
抽象化という意味はつかめたでしょうか。プログラミングの世界では、抽象化が好まれます。頻繁に抽象化がされると具体的には何をしているのかわからなくなることもありますが、抽象化の意味を十分に理解することは大変重要なことです。
これまでの分類・共通化・抽象化を整理してみます。まず、生物というカテゴリーの中に犬と人間というクラスを分類して見ます。
生物クラス 属性 手、足 メソッド 呼吸 メソッド 思考
犬クラス 属性 尻尾
人間クラス 属性 職業 メソッド 仕事をする
エンティティの分類・共通化・継承・抽象化
動物クラスと人間クラスは生物クラスを継承していま。生物クラスは、動物クラスと人間クラスの抽象化です。
またクラスは抽象的で、インスタンスが具体的ということもできます。
処理の分類・共通化・継承・抽象化
処理の共通化とは犬にも人間にも共通してある、呼吸するメソッドで、両方とも生物クラスから継承します。差分は、犬と人間の違う思考メソッドで、生物クラスでは抽象化され、犬と人間の各クラスで実装します。また、犬と人間とは全く別のメソッドを持つこともあります。
抽象化の話をさらに掘り下げて、インターフェースという考え方についてこちらで取り上げています。