オブジェクト指向とは、プログラミングをする上でなくてはならない概念だ。
プログラミングをする上で避けて通れないが、理解するのが難しい。
- モノとして考えるんだ
- 現実世界と一緒だ
- 車を例に挙げてみよう
と言われてもわからないものはわからない。
オブジェクト指向とはいったいどんな考え方なのか? このページでは、オブジェクト指向のイメージを掴んでもらえるように、できるだけ簡単に説明した。オブジェクト指向について知りたい方は参考にしてほしい。
目次
オブジェクト指向とは?の前に
オブジェクト指向に明確な説明はない
まず、はじめに理解しておきたいことがある。オブジェクト指向は「概念」だ、ということだ。概念という言葉自体も難しいが、なんとなく意味はわかるだろう。物事や対象を丸ごとひっくる見たときの大まかな理解のことだ。
なんとなく意味はわかっても、人それぞれ解答が違うだろう。時代や国によっても答えは違うはずだ。
オブジェクト指向もこのように曖昧なものだと思ってほしい。愛や国家ほど漠然としたものではないが、人によって「どこが本質か?」「何が正しい説明か?」が違う。
だから、オブジェクト指向は難しく感じる
オブジェクト指向について説明している技術書やWebサイトは数多く存在するが、どの説明もバラバラだ。言葉も無駄に難しい。語っている本人たちは理解して語っているが、それを読む初心者にはたまったものではない。
あるサイトでオブジェクト指向について調べてなんとなく理解できたようでも、別のサイトを見て説明が違っている場合、理解したと思っていたことが本当に正しいかどうかわからなくなる。
実際、10年やっていても20年やっていてもどこが本質か?の答えはエンジニアによってバラける。
100%理解しようとするからよくわからなくなる。
「ベテランエンジニアは色々とごちゃごちゃ言っているけど、なんとなくこんなもんかな?」くらいの理解で構わない。その状態になれただけで十分な成長だ。
あとは自分の成長と共に、理解が進んでいくし、自分の中で定義ができるようになるだろう。
オブジェクト指向とは?
それでは、オブジェクト指向の説明に移っていこう。
オブジェクト指向は対象を操作するイメージ
オブジェクト指向プログラミング(Object Oriented Programming: OOP)とは、プログラムを手順ではなくて、モノの作成と操作として見る考え方だ。オブジェクトとは「モノ」を意味する。
「テレビ」というモノを操作するとき、中でどういうプログラムが動いているか知る必要はない。リモコンで操作すれば、動く。
「こういう”モノ”を作りましょう」「そして、その"モノ"を使いましょう」というのがオブジェクト指向という考え方だと思っていい。なぜモノを作っておくと便利なのか?
大変な作業を無くす
プログラムというのは上から順番に動作手順を書いていけばとりあえずは動く。しかし、場合によっては大変だ。
次のレーシングゲームを想像しよう。
- ○○車はAボタンを押すと走る。Bボタンを押すと止まる
- △△車はAボタンを押すと走る。Bボタンを押すと止まる
- □□車はAボタンを押すと走る。Bボタンを押すと止まる
とそれぞれプログラミングしてもいいが、1万種類車種があったらどうだろうか? このプログラミング担当者は苦痛だろう。
なんとかプログラミングをこなしても、途中でマネージャーが「やっぱりCボタンを押したらバックする機能を追加しよう!」と言い出したらどうだろうか?
もちろん1万個のプログラムを全部書き換えることになってしまう。絶望を感じるだろう。
そうならないために、「車」というモノをはじめから定義しておいて、それを使ったほうが楽だ。
- 「車」というモノを先に作っておいて、Aボタンを押すと走る。Bボタンを押すと止まるようにしておく
- ○○車は「車」をコピーして外装の色だけ赤にする
- △△車は「車」をコピーして外装の色だけ青にする
- □□車は「車」をコピーして外装の色だけ緑にする
こうしておけば、「Cボタンでバック」という機能を追加するとき「車」プログラムだけを変更すればいい。
大人数で開発するときに便利
「車」というモノを用意しておけば、大人数で開発するときにも便利だ。「車」というプログラムは作成者だけが中身を知っていればよく、他の人は「Aボタンを押すと走る。Bボタンを押すと止まる」だけ知っておけばいいことになる。
手順を全部書いたプログラムだと、他のプログラマーも中身を理解していないといけない。下手したらプログラムを壊してしまうかもしれないからだ。
モノを用意して、それを他の人が触れないようにしておけば、他の人がプログラムを壊してしまう心配がなくなる。
同じようなモノを作りやすい
レーシングカーにトラックを登場させるとしよう。「Aボタンを押すと走る。Bボタンを押すと止まる」という機能は車と一緒だ。
この機能は再利用すべきだろう。これもオブジェクト指向の考え方だ。
すでにあるモノをうまく使って、加工したモノを作れば効率は良くなる。
オブジェクト指向の基本用語解説
オブジェクト指向の考え方と、なぜその考え方が大切かイメージできただろうか? ここから詳細に入っていく。わかるところまで付いてきて頂きたい。
オブジェクト指向には基本となる用語がある。これらはすべて理解して覚えておきたい。
オブジェクト(object)
オブジェクトは、オブジェクト指向の根本だ。
先ほども出てきたが、オブジェクトとは「対象」「物」という意味で、プログラミングにおいてはデータと処理の集まりを意味している。オブジェクト指向で現実のものを例えると、このブログを見ているあなたもオブジェクトであり、使っているPCやスマホもオブジェクトだと言える。
あなたには「名前」や「性別」といったデータ(個体の情報)があり、何かを「見る」「聞く」「話す」といった処理を持っている。
下で説明するクラスとの違いがわかりにくいが、設計図から作った実物がオブジェクトだ。下図を眺めてから次の説明を確認してほしい。
クラス(class)
クラスとはオブジェクトの設計書のようなもので、オブジェクトの中のプロパティやメソッドをひとまとめにしたものだ。
例えば実際に作られた車はオブジェクトだが、車の設計図はクラスだ。割と抽象的な概念なので、ここでは「クラスとは設計書である」と覚えておこう。
プロパティ(property)
オブジェクトが持っているデータのことをプロパティ(属性)と言う。車の例えだと、車というオブジェクトは「メーカー」、「排気量」、「色」といったプロパティを持っていると言える。データにはさまざまな種類があり、たとえば「速度」などといったオブジェクトの状態を示すものもある。
メソッド(method)
メソッド(操作)とは、オブジェクトが持っている処理のことで、車の例だと「走る」、「曲がる」「止まる」など、オブジェクトが何らかのアクションを起こす処理のことだ。このオブジェクトで起こすことができるアクションのことを「振る舞い」とも言うケースもあるので、覚えておいてほしい。
クラス・プロパティ・メソッドはJavaで書くと次のようになる。
Class クラス名{ プロパティの宣言; void メソッド名(){ 処理; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Car { String maker; // メーカー int displacement; // 排気量 String color; // 色 void start() { System.out.println("発進します"); } void stop() { System.out.println("止まります"); } void curve() { System.out.println("曲がります"); } } |
インスタンス化(instance)
インスタンスとは「実体」「事例」という意味で、プログラムでオブジェクトを実際に使う時に生み出されるものである。
設計図からオブジェクトを作ることをインスタンス化と呼ぶ。
Javaでは、次のように記述する。
クラス名 参照変数 = new クラス名();
c1 = new Car();
カプセル化
オブジェクトが持つデータや処理のうち、別のオブジェクトから直接利用される必要のないものを隠すことを言い、利用する場合は外部から操作するために作られた処理を設けることを言う。先ほどのレーシングカーの例でも出てきた、オブジェクト指向の基本概念だ。
プログラムが壊れにくくなるし、大人数で開発をするときすべてのコードを認識する必要がなくなる。
継承
特定のオブジェクトの機能を引き継いで使うことを継承と言う。レーシングカーの例だと"車"からトラックを作るという話があったが、あれが正しく継承だ。
似たようなオブジェクトを複数作る時に、全てのプロパティやメソッドをいちいちプログラミングするのは非常に手間が掛かるが、継承を使うことにより、同じ機能を実装できる。これについては後ほど紹介するサンプルプログラムを見ると分かりやすいだろう。
ポリモーフィズム
ポリモーフィズムもオブジェクト指向プログラミングの基本性質だ。
世の中の家電は、説明書を見なくてもだいたい使い方がわかるだろう。また、車はどの車種であってもアクセルが右というふうに決まっている。
同様にプログラムも同じ処理の名前で動いてくれると、処理名を覚える必要もないし、ミスも減らせてハッピーだ。
クラスによって同一のメソッドで異なる処理が行えるという性質をポリモーフィズムという。
よくわからない部分もあると思うが、これに関しては学習を進めてから理解すればいい。名前だけ覚えておこう。
勇者に学ぶオブジェクト指向プログラミング
ある程度基本となるオブジェクト指向の用語を説明したが、理解できただろうか?
さらに理解が進められるように、ここでJavaのサンプルプログラムを紹介する。プログラミング初心者でJavaがよく分からないという方は飛ばしていただいて構わない。
サンプルプログラムはRPG(ロールプレイングゲーム)風のシチュエーションにしている。勇者、魔法使い、僧侶の3人のパーティが、ラスボスと戦うシーンを思い描いてほしい。
人間クラス(Human.java)
RPGに登場する人間の基本的なプロパティ、メソッドを設定する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
public class Human { private String name = null; //なまえ private int gender = 0; //性別(1:男 2:女) private int length = 0; //身長 private int weight = 0; //体重 private int vitality = 0; //たいりょく private int magic = 0; //まりょく //コンストラクタ public Human(){ } //getter・setter(なまえ) public String getName() { return name; } public void setName(String name) { this.name = name; } //getter・setter(性別) public int getGender() { return gender; } public void setGender(int gender) { this.gender = gender; } //getter・setter(身長) public int getLength() { return length; } public void setLength(int length) { this.length = length; } //getter・setter(体重) public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } //getter・setter(たいりょく) public int getVitality() { return vitality; } public void setVitality(int vitality) { this.vitality = vitality; } //getter・setter(まりょく) public int getMagic() { return magic; } public void setMagic(int magic) { this.magic = magic; } //はなす public void talk(String about){ System.out.println(about); } //たべる public void eatFood(String food){ int foodType = 0; if(food == "やくそう"){ foodType = 1; }else if (food == "まほうのみず"){ foodType = 2; }else{ foodType = 3; } digestFood(foodType); //食べ物を消化する } //消化する private void digestFood(int foodType){ if(foodType == 1){ vitality += 10; //たいりょくを10回復 }else if (foodType == 2){ magic += 10; //まりょくを10回復 }else{ vitality += 1; //たいりょくを1回復 } } } |
勇者クラス(Yuusya.java)
人間クラスを継承したクラスで、勇者特有の必殺技メソッドを持つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class Yuusya extends Human { //コンストラクタ public Yuusya(){ super.setName("ゆうしゃ"); super.setGender(1); super.setLength(180); super.setWeight(70); super.setVitality(100); super.setMagic(5); } public void specialAttack(Human target){ String name = super.getName(); super.setVitality(super.getVitality() - 20); System.out.println( name + " の こうげき"); System.out.println( name + " の ひっさつわざが さくれつした!"); target.setVitality(target.getVitality() - 50); System.out.println( target.getName() + " に 50 のダメージを あたえた"); super.setVitality(super.getVitality() - 10); System.out.println( name + " の たいりょくは" + super.getVitality() + "になった"); System.out.println(""); } } |
魔法使いクラス(Wizard.java)
人間クラスを継承したクラスで、魔法使い特有の攻撃魔法メソッドを持つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class Wizard extends Human { //コンストラクタ public Wizard(){ super.setName("まほうつかい"); super.setGender(1); super.setLength(170); super.setWeight(60); super.setVitality(20); super.setMagic(50); } public void magicAttack(Human target){ String name = super.getName(); System.out.println( name + " の こうげき"); System.out.println( name + " は こうげきじゅもん を となえた!"); target.setVitality(target.getVitality() - 20); System.out.println( target.getName() + " に 20 のダメージを あたえた"); super.setMagic(super.getMagic() - 10); System.out.println( name + " の まりょくは " + super.getMagic() + " になった"); System.out.println(""); } } |
僧侶クラス(Cleric.java)
人間クラスを継承したクラスで、回復魔法のメソッドを持つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Cleric extends Human { //コンストラクタ public Cleric(){ super.setName("そうりょ"); super.setGender(2); super.setLength(160); super.setWeight(50); super.setVitality(20); super.setMagic(70); } //かいふくじゅもん public void healingMagic(Human target){ String name = super.getName(); System.out.println( name + " は かいふくじゅもんをとなえた!"); target.setVitality(target.getVitality() + 20); System.out.println( target.getName() + " の たいりょくは " + target.getVitality() + " になった"); target.setMagic(super.getMagic() - 10); System.out.println( name + " の まりょくは " + super.getMagic()+ " になった"); System.out.println(""); } } |
ラスボスクラス(LastBoss.java)
人間クラスを継承しており、ラスボスの邪悪な必殺技メソッドを持つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class LastBoss extends Human{ //コンストラクタ public LastBoss(){ super.setName("らすぼす"); super.setGender(1); super.setLength(210); super.setWeight(120); super.setVitality(500); super.setMagic(40); } public void specialEvilAttack(Human target){ String name = super.getName(); System.out.println( name + " の こうげき"); System.out.println( name + " の じゃあくな ひっさつわざが さくれつした!"); target.setVitality(target.getVitality() - 30); System.out.println( target.getName() + " は 30 のダメージをうけた"); super.setVitality(super.getVitality() - 10); System.out.println( name + " の たいりょくは" + super.getVitality() + "になった"); System.out.println(""); } } |
メイン処理(main.java)
プログラムの主処理を記述する。それぞれのオブジェクトを呼び出してRPG風のメッセージを表示する。また、最後に各キャラクターのステータスを表示する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public class main { //メイン処理 public static void main(String[] args){ Yuusya yuusya = new Yuusya(); //勇者オブジェクトを生成 Wizard wizard = new Wizard(); //魔法使いオブジェクトを生成 Cleric crelic = new Cleric(); //僧侶オブジェクトを生成 LastBoss lastBoss = new LastBoss(); //ラスボスオブジェクトを生成 //敵があらわれた! System.out.println( lastBoss.getName() + " が あらわれた!"); //バトル開始 yuusya.specialAttack(lastBoss); //勇者の攻撃 wizard.magicAttack(lastBoss); //魔法使いの攻撃 lastBoss.specialEvilAttack(yuusya); //ラスボスの攻撃 crelic.healingMagic(yuusya); //僧侶が勇者を回復 //回復アイテムを使用 System.out.println(yuusya.getName() + " は やくそう をつかった"); yuusya.eatFood("やくそう"); System.out.println(wizard.getName() + " は まほうのみず をつかった"); wizard.eatFood("まほうのみず"); System.out.println(""); //ステータス表示 showStatus(yuusya); showStatus(wizard); showStatus(crelic); showStatus(lastBoss); } //ステータス表示メソッド private static void showStatus(Human target){ System.out.println("- " + target.getName() + " の ステータス---"); if(target.getGender() == 1){ System.out.println("せいべつ : 男"); }else{ System.out.println( "せいべつ : 女"); } System.out.println("しんちょう : " + target.getLength()); System.out.println("たいじゅう : " + target.getWeight()); System.out.println("たいりょく : " + target.getVitality()); System.out.println("まりょく : " + target.getMagic()); System.out.println(""); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
らすぼす が あらわれた! ゆうしゃ の こうげき ゆうしゃ の ひっさつわざが さくれつした! らすぼす に 50 のダメージを あたえた ゆうしゃ の たいりょくは70になった まほうつかい の こうげき まほうつかい は こうげきじゅもん を となえた! らすぼす に 20 のダメージを あたえた まほうつかい の まりょくは 40 になった らすぼす の こうげき らすぼす の じゃあくな ひっさつわざが さくれつした! ゆうしゃ は 30 のダメージをうけた らすぼす の たいりょくは420になった そうりょ は かいふくじゅもんをとなえた! ゆうしゃ の たいりょくは 60 になった そうりょ の まりょくは 70 になった ゆうしゃ は やくそう をつかった まほうつかい は まほうのみず をつかった - ゆうしゃ の ステータス--- せいべつ : 男 しんちょう : 180 たいじゅう : 70 たいりょく : 70 まりょく : 60 - まほうつかい の ステータス--- せいべつ : 男 しんちょう : 170 たいじゅう : 60 たいりょく : 20 まりょく : 50 - そうりょ の ステータス--- せいべつ : 女 しんちょう : 160 たいじゅう : 50 たいりょく : 20 まりょく : 70 - らすぼす の ステータス--- せいべつ : 男 しんちょう : 210 たいじゅう : 120 たいりょく : 420 まりょく : 40 |
サンプルプログラムの解説
それでは解説していこう。まず各キャラクターのベースとなるHumanクラスから。
Humanクラス
Humanクラスには身長、体重、体力などRPGのキャラクターのベースとなるプロパティや、話す(talk)、食べる(eatFood)などのメソッドが設定されている。各プロパティは公開範囲がprivateなので、データを参照したり変更したりするには「setName」、「getName」といったsetter、getterと呼ばれるメソッドを使う必要がある。消化する(digestFood)というメソッドもあるが、これは体の内部のことで公開する必要もないため、カプセル化の際に隠しているのがわかる。
Yuusya、Wizard、Crelic、LastBossクラス
それぞれのクラス名の右側に「extends Human」と書かれているが、これはHumanクラスを継承することを意味している。Humanクラスを継承することにより、話す、食べるといったHumanクラスが持つ人間の基本動作をそのまま利用することができるのだ。
さらに、継承した機能に加えて、それぞれのキャラクターは必殺技や魔法攻撃のメソッドを持っている。継承した上で追加のメソッドやプロパティを設定することで、別の機能を実装できるのだ。
また、最初に呼び出される際、「コンストラクタ」と呼ばれる処理によって、各キャラクターの身長、体重、体力などが設定される。コンストラクタとはオブジェクトを最初に作る時に実行される処理で、オブジェクトの初期化処理などを書くケースが多い。
メイン処理
ここではプログラムの流れが書かれている。最初に各キャラクターのオブジェクトを生成しており、ここで初めてオブジェクトに命が吹き込まれてRPG風の戦闘が始まる。各キャラクターの攻撃メソッドにターゲットを指定して呼び出せば、ターゲットの体力が減少する。僧侶の場合はターゲットを指定して回復メソッドを呼び出せば、ターゲットの体力が回復する処理となる。
メソッドのターゲットの指定には、Humanクラスを設定している。Humanクラスは全キャラクターが継承しているクラスなので、どのキャラクターでも対象として指定できるのだ。
回復アイテムを使用すれば、アイテムによって回復するプロパティや分量が変わるように設計されている。Humanクラスの食べるメソッド(eatFood)を呼び出すことで、Humanクラスで設定された消化メソッド(digestFood)が呼び出され、回復する仕組みだ。消化メソッドはカプセル化によって隠されているので、呼び出し側は消化まで意識する必要はない。
そして最後に各キャラクターのステータスを表示している。最初に生成したオブジェクトごとにプロパティの値は異なっており、また戦いによるダメージによって各キャラクターのプロパティが変わっているのがわかるだろう。
実際にプログラミングしながら理解を深める
オブジェクト指向の基本について、できるだけ分かりやすく紹介したがいかがだったろうか?
オブジェクト指向とはいったいどのようなものか、イメージだけでも掴んでもらえたら幸いだ。もちろん、ここで紹介した内容がオブジェクト指向の全てではなく、理解するには更に知識を深める必要がある。
おすすめなのは、技術書などを読んでみるだけではなく、実際にプログラミングしながら理解することだ。そうすることにより、プログラミングスキルを身に着けながら、オブジェクト指向に関する理解も深まりやすい。
まずは上記プログラムを組んでみることをオススメする。JavaがわからなければPHPでも何でも構わない。Webで検索するとたくさんの実例が出てくる。トライしてみよう。
大変分かりやすかったです
継承だのなんだので悟空と悟飯が〜とか説明されてさっぱりわからなかったのですが、ここのプログラム見てたら理解できました
嬉しいコメントをありがとうございます。引き続きご愛読のほど、よろしくお願い致します。
自分が何気なく使ってるメソッドやプロパティがどういった概念の上でなんの役割を成しているのかが具体例を通してよく理解できました、大変分かりやすい説明でした。
コメントありがとうございます。引き続きご愛顧いただけますと幸いです。
非常に分りやすかったです!
ぜひswiftに置き換えたオブジェクト指向もご教授願います!
今まで見た、オブジェクト指向の説明で一番わかりやすかった。
20年ぶりにまたプログラミングしてみようかな…
お褒めのコメントをいただきありがとうございます。
こらからも分かりやすい内容を投稿してまいりますので、
引き続きご愛顧いただけますと幸いです。
AsReaderと言うBarcode Readerのハード営業ですが、受託開発も受ける為、開発言語が理解出ない事があり、このsite は初心者への配慮があり、これからも利用させて頂きます。ありがとうございました。
いつもご愛読いただきありがとうございます。
また、お褒めの言葉をいただきありがとうございました。
引き続き、何卒よろしくお願いいたします。
様々な箇所で引数として(Human target)というものが使われており、
さらにtarget.getName()や、target.getVitality()というコードもありますが、
この引数(target)とはなんなのでしょうか。
targetという1種類の書き方でその対象が、らすぼすやゆうしゃになるのはなぜでしょうか。
いつもご愛読ありがとうございます。
引数targetとは、人間クラスの変数(入れ物)です。
targetが勇者やラスボスとして振る舞えるのは、勇者クラスやラスボスクラスが人間クラスを継承しているからです。
上記のコードでは、勇者クラス・魔法使いクラス・僧侶クラス・ラスボスクラスは、全て人間クラスを継承したクラスとして定義されています。
このような場合、勇者クラスやラスボスクラスから生み出されたインスタンス(実体)は、人間クラスの変数(Human target)に代入することができます。
つまり、勇者やラスボスといった厳密には異なるインスタンスを、人間とみなし、共通の振る舞い(人間クラスのメソッド呼び出し)をさせることができる訳です。
このような機能は特にポリモーフィズムと呼ばれています。
superはどこから出てきたのでしょうか?
いつもご愛読いただきありがとうございます。
Javaで親クラスの変数やメソッドに子クラスからアクセスしたいときに、
superを使用します。
superを使用するには、クラスの継承を先に行う必要があります。
例)人間クラス(親)を勇者クラス(子)が継承すると、
superを使うことで人間クラスのもっているメソッドを勇者クラスで使用できます。
オブジェクト指向の必要性が分かり、勉強になりました。
ありがとうございました。