300 likes | 406 Views
オブジェクト指向の考え方. 竹内 亮太. はじめに. この資料は、オブジェクト指向プログラミングの理解を補助するためのものです 世の中には「オブジェクト指向とは何か」を 説いた書籍や資料は多いですが、オブジェクト指向への移行の仕方や、その具体的な意味や 意義について述べたものは少ないのが実情です なので、作ってみました. 前提知識. プログラミング演習の内容は大前提です 変数とは何か 配列とは何か メソッドとは何か 引数や返値までしっかり復習すること その他、条件分岐や繰り返しの構文 Java 言語に限定した内容ではありません C++ 言語などにも応用できます.
E N D
オブジェクト指向の考え方 竹内 亮太
はじめに • この資料は、オブジェクト指向プログラミングの理解を補助するためのものです • 世の中には「オブジェクト指向とは何か」を説いた書籍や資料は多いですが、オブジェクト指向への移行の仕方や、その具体的な意味や意義について述べたものは少ないのが実情です • なので、作ってみました
前提知識 • プログラミング演習の内容は大前提です • 変数とは何か • 配列とは何か • メソッドとは何か • 引数や返値までしっかり復習すること • その他、条件分岐や繰り返しの構文 • Java言語に限定した内容ではありません • C++言語などにも応用できます
理解のフェイズ • オブジェクト指向は、フェイズ(段階)に分けて理解していくことをおすすめします • 自分に理解できて応用できそうな要素を、段階を踏んで導入していけばよいでしょう • 一度理解したフェイズも、時間をおいて復習してみましょう • 理解の質が変わってくるはずです
変数のおさらい • 値を覚えておくための箱のようなもの • 1つの箱に1つだけ、値を入れておける • 入れる値の種類に応じて、違う型を使う • 整数なら int 型、実数なら double 型など int iA = 12; iA int型 12
変数の限界 • 変数1つには、1つの値しか入れられない • だが実際には、1つの数値だけでは表しきれない物事が多数存在する • 例:RPGのキャラクターステータス • 名前、職業、レベル、HP、MP、力、素早さ、etc… • 例:三角形や長方形などの図形情報 • 幅と高さ、のように複数の値でないと表現不可
キャラクターを表現してみよう • 右のようなステータス画面のゲームの場合 String sName = “剣士子”; String sClass = “Swordman”; int iLevel = 4; int iExp = 0; int iNowHP = 150; int iMaxHP = 150; int iStr = 31, iVit = 28; int iDex = 14, iAgl = 11; int iInt = 7, iWis = 10; • だいたいこんな感じでできています 剣士子 Swordman
さっきのゲームが4人パーティでプレイするゲームだったとするさっきのゲームが4人パーティでプレイするゲームだったとする あと3人分も、あんな感じで変数を用意するのか? …やってらんねぇ! String charaB_sName = “エルフっ娘”; String charaB_sClass = “Archer”; int charaB_iLevel = 54; int charaB_iExp = 65535; int charaB_iNowHP = 420; int charaB_iMaxHP = 420; int charaB_iStr = 123, charaB_iVit = 119; int charaB_iDex = 241, charaB_iAgl = 180; int charaB_iInt = 176, charaB_iWis = 155; ↑これがあと2キャラ分必要になる キャラクターは1人じゃない
「クラス」の誕生 • 複数の値で何か1つの対象を表す時に、その値をまとめておきたい • まとめた上で、そのまとまったものを「1つの変数として」扱えたら便利かも? • じゃあ必要な変数をまとめたものを「クラス」と呼ぶことにしよう • 作ったクラスは int や double みたいに、「変数の型の1種類」として扱えることにしよう
クラスの作り方 • だいたい授業で説明したとおり public class Character { public String sName; public String sClass; public int iLevel, iExp; public int iNowHP, iMaxHP; public int iStr, iVit, iDex, iAgl, iInt, iWis; } • この段階では、まだ「新しい型の変数の箱」を設計した(デザインした)だけにすぎない
String型 String型 int型 int型 int型 クラスの使い方 • 「クラス型変数」には、自分で決めた「メンバ変数」の分だけ、別々に値をしまうことができる • こうやって使う Character charaA = new Character(); charaA.sName = “剣士子”; • 以下のようなイメージ(一部メンバ変数だけ抜粋) sName sClass iLv iHP iStr Character型 charaA
String型 String型 String型 String型 String型 String型 int型 int型 int型 int型 int型 int型 int型 int型 int型 sName sName sName sClass sClass sClass iLv iLv iLv iHP iHP iHP iStr iStr iStr Character型 Character型 Character型 charaD charaB charaC 量産しよう、そうしよう • 一回作ったクラスは、いくらでも量産して使い回しが可能 Character charaB = new Character(); Character charaC = new Character(); Character charaD = new Character(); • 変数1つでキャラクター1人を表せている
フェイズ1:クラスとは • 何かを表すための値の集まり • 値の集まりを、1つの変数のように扱える • クラスを作るということは、オリジナルの変数の箱をデザインするようなもの • 作ったクラスを利用する時は、 クラス名 変数名 = new クラス名(); として、クラス型変数を宣言して利用する • こうして作ったクラス型変数を「インスタンス」と呼ぶ • インスタンスを作ることを「実体化」とも言う
C++の場合 • 変数の作り方が2種類ある • 状況に応じて使い分けが必要 • 静的な作り方(らくちん) • Character chara1; • 動的な作り方(Java的) • Character *chara1 = new Character; • C++ではnewしたら後片付け(delete)が必要 • 詳細は今後述べる
値をまとめたその次は • 例:キャラクターのレベルアップ処理 charaA.iLevel++; charaA.iMaxHP+=20; charaA.iStr+=5; … • いちいち「charaA.~」とするのは面倒 • charaBに対しても同じように書かなくてはならないなら、値をまとめた意味が薄くなる • 値をまとめたら、それに関する処理もまとめたくなってくる • むしろまとめるべき
メソッド化しよう • クラスのメンバに関する処理は、可能な限りメソッド化する • レベルアップ処理のメソッド public void incremntLevel() { iLevel++; iMaxHP+=20; iStr+=5; … } • 呼び出し側からの呼び出し charaA.incremntLevel(); // charaA がレベルアップ charaB.incremntLevel(); // charaB がレベルアップ
メソッド化のメリット • 同じような処理を何度も書かずに済む • お決まりの手続きを1回の呼び出しで済ませる • 複数のオブジェクトに対しても、呼び出し一発 • メンバ変数を好き勝手にいじらせない • メソッドの中で値をチェックすることで、入れて欲しくない値を弾くことができる • HPがマイナスになったらまずい、などなど
直接操作と間接操作 • 例:ダメージを与える処理 • HPが20の時に、50のダメージが当たった • メソッドを使わない場合 charaA.iNowHP-=50; // 現在HPが-30になる • メソッド化した場合 public void damage(int argDmg) { iNowHP-=argDmg; if(iNowHP < 0) { iNowHP = 0; } } ~~(↑はクラスの内部・↓は呼び出し側)~~ charaA.damage(50); // マイナスになる心配がなくなる!
アクセス制御 • せっかくメソッドを用意しても、どこかで直接値をいじられたら意味がない • クラスの外からいじれるもの、いじって欲しくないものを分ける必要が出てくる • 外からいじってもいいメンバには • public を型の前につける • 外からいじられたくないメンバには • private を型の前につける
全部 public じゃダメなん? • ダメです • 小規模なプログラムでは問題ないですが、好き勝手にいじれる状態にしておくと、後々混乱の原因になります • 基本的に変数は private にしておく • 各変数に対しては、値をセットしたり返してくれたりするメソッドを public で用意しておく • メンバ変数を読み書きするためのメソッドを、setter や getter と呼ぶ
いちいち set したり get したり • 面倒ですよね • 本来ならば、間に値をチェックしたりする必要がなければ、直接代入したり参照しても構いません(クラスの利用目的にもよります) • ですが、今は練習がてらアクセッサ(setter & getter)を作る作業をして、慣れてください
特別なメソッド:コンストラクタ • クラス型変数を new するのと同時に、入れておきたい値、しておきたい処理を書いておくメソッド • キャラクターなら、レベル1のステータスを入れておきたい、など • クラスと同じ名前のメソッドを作ると、それがコンストラクタになる • 返値の型は指定しない • コンストラクタは引数を取れる • 引数を付けたら、new する時に引数を渡す必要がある
コンストラクタの例 • キャラクターを作る時に、名前と職業だけ決めて、後のメンバ変数にはレベル1のステータスをセットしたい // コンストラクタの作成例 public Character(String argName, String argClass) { sName = argName; sClass = argClass; iLevel = 1; … } ~~(↑はクラスの内部・↓は呼び出し側)~~ // new する側はこんな感じ Character charaA = new Character(“エルフたん”, “Archer”);
フェイズ2:メソッドとアクセス制御 • クラスのメンバ変数に対する処理をまとめたものを、メソッドと呼ぶ • メンバ変数とメソッドを持つことで、クラスの本来の意義が完成する • メンバ変数へのアクセスはメソッドを通じて行い、直接いじったりはしない • それを徹底させるため、public や private などのキーワードを指定し、アクセスを制御する • new するついでに色々やらせることが可能 • コンストラクタ
次のフェイズに向けて • 次のフェイズはちょっと次元跳躍します • これまでの内容でも、オブジェクト指向のメリットは大きいはずです • 十分プログラムの書き方は効率的になってます • ですが、これからが真骨頂です • しっかり復習してから進みましょう
例:図形 三角形 長方形 円 直方体 球 円錐 … 例:RPGのキャラクター 戦士 ナイト 僧侶 魔法使い ハンター 盗賊 アサシン 司祭 賢者 パラディン … 色んなものをクラスで表そう
共通点があるもの多いよね? • 図形は全て「図形」という括りにできる • 更に言うなら平面図形と立体図形に分けられる • 職業も、全て「キャラクター」で括れる • 更に言うなら、各職業には「上位」のものがあったりする • ナイト→パラディン • 僧侶→司祭 • 魔法使い→賢者
もっとクラス化を効率よく • 考え方その1 • 共通点だけを括りだしてクラスにする • 例:「図形」という抽象的なクラス • それに個別のメンバを足して、「継承」したクラスを作る • 例:「三角形・直方体」などの具体的なクラス • 考え方その2 • 基本的な機能(能力)だけを持ったクラス • 例:「ナイト・僧侶・魔法使い」など • それを「継承」し、追加機能(能力)を持たせたクラス • 例:「パラディン・司祭・賢者」など
図形 平面図形 長方形 三角形 円 立体図形 直方体 円錐 球 キャラクター ナイト パラディン 僧侶 司祭 魔法使い 賢者 ハンター レンジャー シーフ アサシン 表現する物事を体系化する
「継承」のメリット • 共通部分をわざわざ書かなくてよい • 付け加えたり、置き換えたい部分だけ作る • 変数の追加 • メソッドの追加 • メソッドの置き換え • こういうことができる↓ • 継承元クラス 変数名 = new 継承先クラス(); • 図形 shape = new 長方形(); • ナイト chara0 = new パラディン(); • 子は親の一種として扱うことができる