クラスとインスタンス(オブジェクト指向)
クラスはくくり、インスタンスはくくりのルールで定義された物。
イメージは車というクラスからプリウスというインスタンスを生み出すって感じ。
オブジェクト指向の本質はものを作って、使って仕組みを作る。モノを作るときは具体的に作り、使うときは抽象的に理解しながら使う
クラス内でやる事
インスタンスが持っておくべき変数や関数を定義する。
またインスタンス作成時にどのように初期設定をするか書かれる。
→コンストラクタ関数を定義し、引数をもらって初期値を作れるようにする。
→インスタンス作成時に引数として値を渡して、各インスタンスに変数の初期値を入れる。
class Person {
private Stirng name; //ここではインスタンスに必要な変数が用意されている。
Person(String name, ...) { ////()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
}
public void detailData() { //ここでインスタンスに必要な関数が書かれる
}
}
補足
・クラス内で書かれているthisは、作られたこのインスタンス自身という意味になる。
private Stirng name; //このクラスでnameという変数を定義
public void nameInput(String input) { //文字列の変数inputを引数にnameInputを実行すると
this.name = input; //このインスタンスの変数nameは受け取ったinputと同じ値になる。
}
・インスタンスのための変数(インスタンスフィード)もあるがクラスのための変数(クラスフィード)も存在する。クラスフィードはクラスの中の変数のため、何かあるたび(例 インスタンスが作られるたび)に変更されるようにしたりする。
class Person {
private static int count; //これはクラスのための変数
private Stirng name; //これはインスタンスのための変数
Person(String name, ...) { ////インスタンス作成のための初期設定
Person.count ++; //これでPersonインスタンスが作成されるたびに、Personクラスのcountが+1される
}
}
クラス名.クラス変数で呼び出したり(これはクラスの外部で使用する時)、self.クラス変数で使ったり(これはクラスの内部で使用する時)する
初期化、コンストラクタメソッド
これはインスタンス作成時、引数を受け取り初期値設定するのに必要な関数(というより初期化関数、このクラスを使用する時は毎回発動する!?)。
しかし初期値設定の引数入力パターンは1種類ではない。例えばmiddle nameがある人は、普通の人より入力する項目が増える。そのためmiddle nameがある人用の入力関数も必要。
↓
引数の入力パターンによって、コンストラクタメソッドもパターンを設ける。
overloadという方法で、複数の引数入力パターンに対応した同名のコンストラクタメソッドを作ることができる。
class Person {
private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。
private Stirng lastname;
private Stirng middlename;
Person(String firstname, String lastname) { //firstnameとlastnameだけの人の初期設定
this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。
this.lastname = lastname;
}
//同じ名前の関数を書く
Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定
this.firstname = firstname;
this.lastname = lastname;
this.middlename = middlename;
}
}
overloadとoverride
overload
overloadは同名の関数を複数作る時、名前は同名でも引数の入力パターンは異なるため、そのパターンから最適な関数を選択してくれること。主に初期設定の関数に行われることが多い。
↓
初期設定関数作成時、オーバーロードで作った関数同士の処理内容が重複している時がある。
↓
例えば初期設定関数である関数Aがある。
関数Aの中で、this(name等)を実装すると
関数Aと同名の初期設定関数を探し、その中でnameの処理をしている関数Bを探してきて、nameの処理を行ってくれる。以下のコードでは一つ前のコードをきれいにしている。
class Person {
private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。
private Stirng lastname;
private Stirng middlename;
Person(String firstname, String lastname) {
this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。
this.lastname = lastname;
}
Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定
this(firstname, lastname); //ここが変わった。同名関数に存在するfirst,lastnameの処理を行った。
this.middlename = middlename;
}
}
override
overrideは親クラスと同名の関数を定義する時に、子クラスの内容を上書きすること。
↓
普通に親クラスの関数と同名の関数を子クラスで定義してしまうと、その関数は親クラスの時の処理内容を引き継がず子クラスの処理内容だけを実行することになる。
↓
親クラスの時の処理内容も子クラスの関数に引き継ぎたいならば、子クラスの関数の中でsuper.メソッド名をしてやらないといけない。
class Person {
.
.
.
public void detailData() { //インスタンスの詳細な情報を表示する関数
System.out.println("名前は" + this.name); //インスタンスの変数を表示
System.out.println("年齢は" + this.age);
}
}
class Child extends Person { //ChildクラスはPersonクラスを継承
.
.
.
public void detailData() { //親であるPersonクラスに同名の関数あり
super.detailData(); //これで親であるPersonクラスにあるdetailData()の処理内容を引き継げる
//ここからchildクラス特有の処理内容を書く
.
.
}
}
カプセル化
カプセル化とは無駄を省いてわかりやすい物を作ること。使うときに抽象的に理解しながら使える(難しいことは考えずに使える)ようにするためにあえて内部の構成などの情報を見せないようにすること。
カプセル化の中の一つの機能をここでは書かせていただきます。
それはあるクラスで定義した変数や関数へのアクセス回路を制限する事(セキュリティーのため?)
↓
変数や関数定義の時に、直前にこれをつけるとアクセス制限ができる。
・public→外部のクラスからのアクセスが可能。
・private→外部のクラスからのアクセスが不可能。その変数、関数を定義したクラスの内部にあるメソッドを使ったアクセスなら可能。
・protected→基本的にprivateと同様に外部のクラスからのアクセスが不可能。しかしその変数、関数を定義したクラスを継承した子クラスならばアクセス可能。
お作法
・インスタンスの情報となるクラスの変数はprivateにして、インスタンスの命令となるクラスの関数はpublicにして外部からアクセスできるようにする。変数を取得、変更するような関数(例 get~()やset~()とか)をクラス内で定義してやる事で、インスタンスの変数の取得、変更を可能にする。
class House{
private Stirng name; //ここでは必要な変数が用意されている。
private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。
House(String name, ...) { //インスタンス初期設定
}
//ここから関数が書かれていく。
public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
}
public getOwner() {
return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない
}
}
名前空間(こちらはPHPの例です、すいません)
名前空間は別の場所のクラスや関数や変数を読み出す時に使用する(require、include)。その時にクラスや関数や変数の名前が同じものがあるとエラーになる。なので、そのためクラスや関数や変数の上に、あえて親的な存在を作成する。それが名前空間。参考文献は【PHP入門】名前空間(namespace/use)の使い方をわかりやすく解説!
クラスの継承(親クラス、子クラス)
extendを使って親クラスを継承した子クラスを作ることができる。
また子クラス内でインスタンスに対する変数の初期値や初期関数の設定を変更できる。
クラスの継承はいくらでもできるのでクラスのクラスのクラスの...ができる。
お作法
・クラス毎にファイルは分割して読みやすくするのが基本。
・継承とは異なるかもしれないがimportをすると外部のライブラリーやパッケージを読み込める。
抽象クラス
様々な子クラスで必ず必要だけれど、クラスによって処理内容が異なる関数がある場合。
↓
親クラスで抽象メソッド(あえて何も処理が書かれていない関数)を定義する。
すると子クラスでは必ずoverrideしてその関数定義をしなければいけない。
また抽象メソッドを持つクラスは抽象クラスと呼ばれ、そのクラス自身のインスタンスは作成できない。
多重継承
javaでは禁止されているが、子クラスが複数の親クラスを継承することを多重継承と呼ぶ
クラス間での交わり
Aクラス内で別のBクラスのインスタンスを変数として定義することができる。例えばHouseクラス内で、所有者を示す変数ownerとしてPersonクラスのインスタンスを紐づけることができる。
class House{
private Stirng name; //ここでは必要な変数が用意されている。
private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。
House(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
}
//ここから関数が書かれていく。
public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
}
public getOwner() {
return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない
}
}
class Person {
private Stirng name; //ここでは必要な変数が用意されている。
Person(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
}
//ここから関数が書かれていく。
public void detailData() { //ここでインスタンスの詳細な情報を表示するようにする
}
}
house1 = New House("Aハウス", ...);
person1 = New Person("山本", ...); //引数を受け取って、それぞれのクラスのインスタンスを作成
house1.setOwner(person1); //これでhouse1のownerがperson1の値と同じになる。
house1.getOwner().detailData(); //これでhouse1のownerの詳細な情報が得られる。
//getOwnerでownerの情報をreturnする(単体では何も表示されない)。その後にPersonで定義した関数を使うことで、ownerの情報を知れる。
変数にインスタンスを入れるということ
newでインスタンスを作る。
引数の値を受け取って、メモリにそのインスタンスのための領域をを確保して、そこにインスタンスの情報を置いておく。(情報は二進数で書かれる)
↓
そいつを変数に代入する。
インスタンスのための変数を作ると、その変数のための領域がメモリ内で確保される。その領域にはインスタンスの領域がどこにあるかという情報だけを残す。
多態性
Aクラスを継承しているBクラスのインスタンスは、Aクラスのインスタンスとしても扱えるしBクラスのインスタンスとしても扱えるぞって感じかな?。
例えば同じ関数処理を別のクラスのインスタンスに実行する場合、それぞれのクラス毎に関数を設定してしまいがち。しかしその異なったクラスが共通の親クラスを継承している場合、その親クラスに対する関数処理を書くだけで良くなる。これにより、その親クラスを継承する子クラスが増えても関数処理を書かなくて良くなる。
やりがちなコード
class House {
.
.
.
public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
}
}
class Villa { //villaは別荘という意味
.
.
.
public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
}
}
class Person {
.
.
.
public void buy(House house) { //引数としてHouseクラスのインスタンスである変数houseを受け取れば
house.setOwner(this); //変数houseのownerには、このインスタンスがなる。
}
public void buy(Villa villa) { //引数としてVillaクラスのインスタンスである変数villaを受け取れば
villa.setOwner(this); //変数villaのownerには、このインスタンスがなる。
}
}
多態性を活かしたコード
(Houseクラスとvillaクラスの共通の親クラスはBuildingクラス)
class Person {
.
.
.
public void buy(Building building) { //引数としてBuildingクラスのインスタンスである変数buildingを受け取れば
building.setOwner(this); //変数buildingのownerには、このインスタンスがなる。
//親クラスに対する関数定義をしてやると子クラスに対する定義が不要になる
}
}
SOLID
オブジェクト指向での大事な考え方。
以下を参考にさせていただきました。
オブジェクト指向設計原則とは
【ボブおじさんのClean Architectureまとめ】オブジェクト指向 ~SOLIDの原則~
Laravelとオブジェクト指向とクリーンアーキテクチャについて理解を深めてみた。
Single Responsibility Principle
そのクラスの中で、機能の定義が完結しとかないといけないよという考え方。
Open-Closed Principle
要するにあらゆるクラスはクラスに依存せず、インターフェースに依存しようってこと。
クラスAの中で、直接的にクラスBのインスタンスを作る(new)のはやめようね、保守するのが大変やから。
じゃあどうする?
Factory methodというデザインパターンで解決。
まずクラスBのインスタンスを作って返してくれる関数を持つB_Factoryクラスを作る。
↓
クラスAはB_Factoryクラスを使用して、間接的にクラスBのインスタンスを得る。
Liskov Substitution Principle
親クラスが使われているところを、子クラスを使用するようにしても動くようにしようねってこと。つまり子クラスは親クラスが持っているプロパティーやメソッドを定義できて無いといかん。
Interface Segregation Principle
保守性を考えて、クラスはインターフェースに依存していこう、でも一つのインターフェスが大きすぎると影響範囲も大きくなりそうだから小さい形にしていこう。
Dependency Inversion Principle
Open-Closed Principleと似ている?
上位のクラスは下位のクラスの実装に依存してはいけないってこと。ここでいう上位とは別のクラスのインスタンスを使っているクラス(例:車)。下位クラスとは別のクラスから自分のクラスのインスタンスが使用されいてるクラス(例:エンジン)。
この解決もOpen-Closed Principleと同じでFactory methodでやる。
その他補足
マジックメソッド(php)
setやgetのこと。
$obj->name = 値; をするとsetが発動する→ここではプロパティー名と値を引数にすることで代入可能。privateプロパティーにもアクセスできる。
$obj->name;でgetが発動する→プロパティー名を指定することで値を得られる。
クラス変数、メソッド
static変数、メソッドとは静的ということで、クラス自体に変数やメソッドが存在するという感じである。メリットはクラス名::メソッドで読みだすことができるのでインスタンスが無くても使えること
インターフェース
インターフェースとは抽象メソッドをいくつかもつモノ。
抽象クラスは多重継承されることが許されていないが、インターフェースでは多重継承される事を許可している。また継承しているインターフェースに含まれている抽象メソッドは必ずoverrideして定義しなければいけない。