オブジェクト指向プログラミングとは
はじめに
前回の「本連載の今後の展開」でも紹介したとおり、今回からはテーマごとに解説をしていきます。第2回のテーマは「オブジェクト指向プログラミング」です。
オブジェクト指向とは
書籍やWebなどでオブジェクト指向の説明を読むと「オブジェクト指向とは、オブジェクト同士の相互作用として、システムの振る舞いをとらえる考え方である。云々」とあります。これでは説明が変に難しく、身構えてしまうか方も多いのではと思います。
しかし、オブジェクト指向は人間が分かり易いように「現実世界のモノをそのままモデリングしたもの」であり、本来は理解し易いはずです。
現実社会のモノとは、例えば「会社」「社員」「飛行機」……「キャベツ」「大根」などもそうですね。このモノを言い換えるとオブジェクトとも言えます。
「オブジェクト」と「クラス」の違い
「オブジェクト」と「クラス」を混同しないように、その違いを簡単に説明しておきます。
クラスとは設計図のようなものです。オブジェクトを生成するために定義された概念です。
例えば、「テレビ」「エアコン」「掃除機」などの家電は、種類は違いますが「家電」としての共通性(概念)を持っています。この共通性をひとまとめにしたものがクラスです(上記の例では家電がクラスと言えます)。
そして、設計図をもとに実体化したものがオブジェクトです(上記の例ではテレビ、エアコン、掃除機がオブジェクトと言えます)。実体化とは、クラスをインスタンス化(コンピュータがメモリを確保した状態)することを指します。
クラス図とオブジェクト図で見てみましょう。いずれもUMLの基本となる図で、クラス図は抽象的な表現のクラスをその構成や相互関係を表した図です。対してオブジェクト図はクラスを実体化(インスタンス化)して、オブジェクト間の関係を表した図です。この2つの図を見比べることで、クラスとオブジェクトの違いをよりイメージしやすくなると思います。
例として、「人間」と「家電」の場合は、人間は家電を所有するので、クラス図で書くと以下のようになります。
クラス図に書かれているクラスを少し説明します。水平線によって3分割されていますが、一番上にクラス名、真ん中に形や性質などの属性(データ)、一番下に操作(振る舞い)を記述します。
次に、オブジェクト図を見てみましょう。「人間」や「家電」は共通的な概念でしかないので、実体化します。例として「鈴木さんはテレビ、エアコン、掃除機を持っています」とした場合のオブジェクト図は以下のようになります。
参考までに、オブジェクト図の見方も説明しておきます。オブジェクト図はインスタンス名、クラス名、変数名、値で構成されます。
まとめると、クラスは人間や家電のような共通的な概念、オブジェクトはクラスを実体化したものです。
クラスやオブジェクト以外にも「継承」「カプセル化」「ポリモーフィズム」など、オブジェクト指向プログラミングで知っておきたい用語はたくさんあります。ここで順に説明していきます。
継承:親クラスの性質を受け継ぐこと
継承はサブクラス(子クラス)が親クラスの性質を受け継ぎます。動物を例にして説明すると、「犬」クラスや「猫」クラスは親クラスである「哺乳類」クラスから「乳で子を育てる」という性質を受け継ぎます。
また、「哺乳類」クラスは親クラスである「脊椎動物」クラスから「骨格として脊椎を持つ」という性質を受け継ぎます。
つまり、「犬」クラスと「猫」クラスは親クラスの性質全て(「骨格として脊椎を持つ」「乳で子を育てる」)を持つことになります。
カプセル化:仕事の内容を隠すこと
カプセル化とは、クラスに一切の仕事(責任)を持たせ、別のクラス(オブジェクト)から仕事の内容を隠すことを言います。
具体的な例で説明しましょう。小学生のひろし君はお母さんに「夕食はカレーを食べたい」と伝えます(ちなみに、この「~と伝えます」はオブジェクト指向では「メッセージを送信する」と言います)。
お母さんは買い物に出かけ、近所の八百屋さんで玉ねぎ、人参、ジャガイモを買い、お肉屋さんで豚肉を買い、スーパーでカレールーを買いました。お母さんは家に帰って買ってきた材料でカレーを作り、ひろし君に「カレーが出来たわよー」と伝えます。
ひろし君は、目の前に出てきたカレーをお母さんがどうやって作ったか知りません。材料の野菜がどの八百屋さんで買われたものかも知りません。八百屋さんまでどうやって行ったのか、豚肉はどのお肉屋さんで買ったのかも知りません。もしかしたら八百屋や肉屋には行かず、全ての材料をスーパーで買ったかも知れません。
お母さんは全てを知っていますが、仕事の内容(カレーを作るまでの作業)は外(ひろし君)からは隠されています。このことをカプセル化と言います。
もし、カプセル化をしないと、ひろし君が八百屋や肉屋を決めて、作り方も指示して...と何から何まで把握する必要があるため、とても大変でプログラムも複雑になってしまいそうです。
カプセル化をすると、お母さんに要件を伝えた後は全てお任せで、待っていれば目の前に勝手にカレーが出来てきます。ひろし君は何も知らなくて済むし、良い意味で仕事の内容が隠されているのでプログラムが簡潔になります。
ポリモーフィズム:同一のメソッドで異なる振る舞いをすること
ポリモーフィズムは、クラスによって同一のメソッドで異なる振る舞いをすることを言います。メソッドとはクラスが持つ機能を定義するためのもので、プログラム実行時に起動される内容を記載します。ポリモーフィズムは「多態性」とも言います。
例えば、「山田さん」クラス、「外国人」クラスを用意し、「外国人」クラスには「挨拶する」というメソッドを定義します。さらに、「外国人」クラスのサブクラスに「中国人」クラス、「アメリカ人」クラス、「フランス人」クラスを作ります。
それぞれのクラスの「挨拶する」メソッドを実行した場合、「ニーハオ」「ハロー」「ボンジュール」というように同じ「挨拶する」メソッドでも挨拶する言葉(振る舞い)が異なります。
「山田さん」クラスが相対しているのは(見えているのは)「外国人」クラスです。つまり、山田さんは相手がどこの国の人かは知りませんが、挨拶すれば該当の言葉で挨拶が返って来ます。
この相手を知らない、メッセージを送れば同一のメソッドで異なる振る舞いをすることをポリモーフィズムと言います。
オブジェクト指向のプログラミング
先ほどのクラス図、つまりポリモーフィズムを取り入れたオブジェクト指向プログラミングでコーディングすると以下のようになります。
【Yamada.java】public class Yamada {
private Foreigner foreigner;
public Yamada(Foreigner foreigner) {
this.foreigner = foreigner;
}
public void greet() {
foreigner.greet();
}
}
【Foreigner.java】public abstract class Foreigner {
public abstract void greet();
}
【Chinese.java】public class Chinese extends Foreigner {
@Override
public void greet() {
System.out.println("ニーハオ");
}
}
【American.java】public class American extends Foreigner {
@Override
public void greet() {
System.out.println("Hello");
}
}
【French.java】public class French extends Foreigner {
@Override
public void greet() {
System.out.println("Bonjour");
}
}
Yamadaクラスはコンストラクタの引数で受け取ったForeigner型のオブジェクトに対して、挨拶する(greet()メソッド)を実行します。コンストラクタとは、オブジェクトを生成する際に呼び出されて内容の初期化などを行なうメソッドのことです。
ポリモーフィズムを取り入れているので、YamadaクラスはForeigner型のオブジェクトがChineseかAmericanかFrenchか実態を知りません。
この「知らないこと」こそが、受け取ったオブジェクトの実態によって異なる振る舞いをするポリモーフィズムの肝と言えます(Chineseクラスのオブジェクトを渡せば「ニーハオ」と挨拶し、Americanクラスのオブジェクトを渡せば「Hello」と挨拶し、Frenchクラスのオブジェクトを渡せば「Bonjour」と挨拶します)。
ちなみに、手続き型プログラミングのような非オブジェクト指向プログラミングでは、以下のようになります。手続き型プログラミングとは、コンピュータがどのように動作すれば処理効率が良いかを考え、プログラム上から順番に処理を実行していくプログラミングのことです。オブジェクト指向プログラミングのように人間が分かりやすいようにプログラミングするのではなく、コンピュータの処理効率重視を優先させます。
【Foreigner.java】public class Foreigner {
public void greet(String foreigner) {
if ("Chinese".equals(foreigner)) {
System.out.println("ニーハオ");
} else if ("American".equals(foreigner)) {
System.out.println("Hello");
} else if ("French".equals(foreigner)) {
System.out.println("Bonjour");
}
}
}
Chineseクラス、Americanクラス、Frenchクラスがなくなり、Foreignerクラスだけになります。1つのクラスをif文で分岐させるコーディングです。
もちろん、これでもオブジェクト指向で作ったプログラミングと同じ動きをしますが、例えば新たに別の外国人を追加する場合、オブジェクト指向プログラミングではForeignerクラスを継承させた新たなクラスを追加するだけですが(既存のクラスやソースコードは一切修正することなく、新しいクラスを追加すればOK)、手続き型プログラミングではif文があるため既存のソースコードを修正しなければなりません(else ifをさらに追加)。
今回の例はサンプルなので単純なif文ですが、通常の業務ではもっと複雑な分岐のコードになるでしょう。複雑な分岐のコードは修正に工数が掛かるだけでなく、修正したことで正常に動かなくなってしまう事態になるかも知れません。
通常の業務では、仕様変更や機能追加、障害対応などシステムを修正しなければならない状況が次々と起こるので、ぜひオブジェクト指向でのプログラミングを考えてみてください。
おわりに
いかがでしたか。今回はオブジェクト指向に必要なクラスやオブジェクト、継承、カプセル化、ポリモーフィズムについて解説しました。
次回は、オブジェクト指向をもとにクラス設計を学んでいきます。