見出し画像

わかりやすいオブジェクト指向

はじめに

プログラミングを初めてしばらくするとオブジェクト指向という言葉に辿り着く方は多いんじゃないでしょうか?

これ結構理解に苦労する方多いと思います。
コードとして表現することよりも、概念の理解で躓いて「まあ書けてはいるから...」と流してる方もいるんじゃないでしょうか。

そこで本記事ではコードとしての表現よりも、概念理解にフォーカスしていきます。

オブジェクト指向とは

オブジェクト指向とは「オブジェクトに重きを置いた」という意味で、一種の記述スタイルです。

他にも手続きに重きを置いた「手続き指向」などがあります。

オブジェクト指向を理解するには、まず「オブジェクト」に対する正しい理解が必要です。

オブジェクトとは

そもそもオブジェクトはプログラミング固有の言葉ではありません。
一般的にオブジェクトという言葉があります。

そこでまずは一般的な「オブジェクト」が何を表すか理解していきましょう。

一般的なオブジェクト

オブジェクトは日本語で「客体」と言うそうです。
つまりオブジェクト=客体が答えです。

と言われても、日常生活ではあまり使わないのでなかなかイメージ浮かばないですよね。

しかし、対義語ならそこそこ馴染みがあるかもしれません。

客体の反対は主体と言います。
「主体的に行動する...」とか「主体性を持って...」みたいな文脈で使われますね。

ここに客体理解のヒントがあります。

主体と客体

まず、主体とは何か?
主体とは自分自身(=私)のことです。

AさんとBさんがいるとします。
この時、Aさんにとっての主体はもちろんAさんで、BさんにはBさんです。

しかし、Aさんから見たBさんは主体ではありません。あくまでAさんにとって主体は自分自身であるAさんだけなのです。逆もまた然りです。

当たり前過ぎますがこれはかなり重要です。
主体とは自分自身のみを指します。

一方この時客体はというと、AさんにとってはBさん、BさんにとってはAさんがそれに当たります。

先ほど主体ではないと定義したものが客体です。
つまり主体は自分、客体とは自分以外となります。
これは複数でも人以外でも適用されます。
この場にテーブルや椅子があればそれらも客体=オブジェクトとなります。

要するに、一般的なオブジェクトとは自分以外のもの全てということです。

では、これをプログラムでどう表現するでしょうか。

プログラムでオブジェクトを表現するためには

プログラムでオブジェクトを表現する場合、まず誰が主体かを定める必要があります。

どの視点から見るかを定めなければ、ある時はオブジェクトだが、ある時はオブジェクトではないと言うことが起こりかねません。

そのため、プログラミングにおける主体はプログラマー自身とします。
コードを書く人を基準にすれば、プログラムは全てオブジェクトにできます。

こうなれば、AさんもBさんも必ずオブジェクトです。

プログラムでオブジェクトを表現する

プログラムはどこまで行っても文字でしか表現できないので、オブジェクトを表現する場合も、例に漏れず文字を使います。
プログラムでは属性と機能の2つでオブジェクトを表現します。

属性と機能では分かりにくいので、もう少し具体的にしましょう。

人間をオブジェクトとして表すなら、年齢や性別などが属性、歩くや話すなどが機能となるでしょう。
このように、属性と機能の集合によってオブジェクトを表現します。
このとき属性をプロパティ、機能をメソッドと呼びます。

そして、属性に具体的な値を当てはめることで固有のオブジェクトを表現します。

このようにAさんなら女性/24歳/東京出身、Bさんなら/男性/31歳/神奈川とします。

ここで用いた人間のテンプレート(属性と機能の定義)をクラスといいます。

そしてAさんやBさん(テンプレートに固有値を入れたもの)はインスタンスと呼びます。

オブジェクトの作り方はこんな感じです。

ただ、オブジェクトは作ることが目的ではありません。
むしろそれらを使ってシステムを構築することに本質があります。

では、次は使い方を見ていきます。

オブジェクトを使う

実際にオブジェクトを使ってシステムを作ってみます。
すると、オブジェクト指向の良いところが見えてきます。

では、2人が互いに挨拶をするという単純なシステムをつくります。

流れはこんな感じです。
Aさんが「Bさんこんにちは!」という
Bさんが「Aさんこんにちは!」と返す。
ただこれだけです。
※概念理解に集中するため、プログラミング言語は使わず自然言語で表現します。

まずは、オブジェクト指向ではない場合。

Bさん、こんにちは!
Aさん、こんにちは!

シンプルなのでたった2行で終わりました。

ではオブジェクト指向で書いてみましょう。

// 名前があり挨拶ができる人間テンプレート

人間テンプレート{

 // その人の名前
 名前

 // 相手の名前を読んでからこんにちはと言う機能
 挨拶する(挨拶する相手){
  *挨拶する相手の名前*さん、こんにちは!
 }
}

// AさんとBさんをつくる
Aさん = 人間テンプレート(名前="Aさん")
Bさん = 人間テンプレート(名前="Bさん")

// お互いに挨拶する
Aさん→挨拶する(Bさん)
Bさん→挨拶する(Aさん)

ちょっと長いですね。
まずは、人間をテンプレートにする→テンプレからAさんとBさんを作る→挨拶をする。

これだけ見ると、オブジェクト指向はなぜこんなめんどくさいことをするのかと思うでしょう。

オブジェクト指向は変更に強い

めんどくさくする理由はこれです。システム変更で優位さを発揮します。

条件が変わることでシステムに手を加えるシーンは多いです。

変更①

例えば、時間によって挨拶の文言を変えたいとします。1日中「こんにちは」なわけないですからね。

  • 6:00〜12:00 → おはよう

  • 12:00〜16:00 → こんにちは

  • 16:00〜6:00 → こんばんは

やってみましょう。
オブジェクト指向では無い場合こうなります。

// 時間によって挨拶文を作る関数
挨拶文をつくる(){

 現在時刻 = 時計の現在時刻

 もし、6:0012:00だったら{
  挨拶文 = おはよう
 }

 もし、12:0016:00だったら{
  挨拶文 = こんにちは
 }

 もし、16:006:00だったら{
  挨拶文 = こんばんは
 }
}

Bさん、*挨拶文をつくる()*!
Aさん、*挨拶文をつくる()*!

挨拶文をつくるという処理を関数としてまとめます。その中で時間で条件分岐して挨拶文を生成します。

ではオブジェクト指向の場合、人間テンプレートをこのように変えます。

// 名前属性と挨拶ができる人間テンプレートをつくる

人間テンプレート{

 // 相手の名前を読んでから時間に応じて挨拶する
 挨拶する(挨拶する相手){

  現在時刻 = 時計の現在時刻

  もし、6:0012:00だったら{
   挨拶文 = おはよう
  }

  もし、12:0016:00だったら{
   挨拶文 = こんにちは
  }

  もし、16:006:00だったら{
   挨拶文 = こんばんは
  }

  *挨拶する相手の名前*さん、*挨拶文*! 
 }
}

オブジェクト指向では無い場合と変更内容は同じです。
しかし、変わるのがテンプレートだけなので、AさんやBさんから→挨拶する()を呼び出す部分は変えなくていいです。

ただ、これぐらいの処理ではオブジェクト指向じゃなくても対処できるので良さが分かりにくいです。

それでは、このぐらい一気に条件が加わったらどうでしょう?

変更②:

  • Aさんは一般社員でBさんは部長

  • 挨拶の後、部長は仕事の進捗を一般社員に尋ねる

  • 一般社員は仕事の進捗を完了、進行中、未対応のいずれかで回答する

だいぶ入り組んできました。

挨拶文をつくる(){
 // ここは同じなので省略
}

Bさん、*挨拶文をつくる()*!
Aさん、*挨拶文をつくる()*!

Aさんの役職 = 一般社員
Bさんの役職 = 部長

もし、Bさんが部長なら{

 Aさん、仕事の進捗はどうですか?

 Aさんの仕事進捗 = 進行中

 もし、進捗が完了なら{
  はい、既に完了しています
 }

 もし、進捗が進行中なら{
  はい、現在対応中です
 }

 もし、進捗が未対応なら{
  申し訳ありません、まだ着手しておりません。
 }
}

挨拶は変わりません。その後に仕事の進捗確認→報告の処理を追加してます。

ではオブジェクト指向の場合はどうでしょう。

// 名前と挨拶ができる人間テンプレートをつくる
人間テンプレート{
 // 同じのため省略
}
// 人間テンプレートをもとにした部長テンプレート
部長テンプレート (人間テンプレートがベース){

 // 進捗を確認する
 進捗確認(一般社員){
  *一般社員の名前*さん、仕事の進捗はどうですか?
 }
}
// 人間テンプレートをもとにした一般社員テンプレート
一般社員テンプレート (人間テンプレートがベース){

 進捗状況属性

 // 進捗を報告する
 進捗報告(){
  もし、進捗状況が完了なら{
   はい、既に完了しています
  }

  もし、進捗状況が進行中なら{
   はい、現在対応中です
  }

  もし、進捗状況が未対応なら{
   申し訳ありません、まだ着手しておりません。
  }
 }
}
// オブジェクトを使ってシステムをつくる

Aさん = 一般社員テンプレート(名前="Aさん",進捗="対応中")
Bさん = 部長テンプレート(名前="Bさん")

Aさん→挨拶する()
Bさん→挨拶する()

Bさん→進捗確認()
Aさん→進捗報告()

なにやら、「部長テンプレート」と「一般社員テンプレート」なるものが出てきました。

オブジェクト指向では、テンプレートからさらにテンプレートをつくることもできます。(これを継承と言います)

進捗を確認するのは部長だけであり、報告するのは一般社員だけです。
しかし、どちらも名前があって挨拶ができるという点では同じです。
テンプレートを個別に用意するのは重複部分が出て無駄です。
同じところは人間テンプレートとして共通化して、それを引き継ぐかたちでそれぞれのテンプレートを作れば楽です。

さらに部長にだけ進捗確認、一般社員にだけ進捗報告機能を持たせれば、誤って部長が報告するあるいはその逆は起こりません。

これぐらい変わるとオブジェクト指向の方が変更しやすいですし、さらなる変更にも対応できそうな気がします。

では、最後にもう一つだけ条件を変えます。

変更③

部長はなぜか、こんにちはは16:00までではなく15:00までだと言い張ってるとします。
これを表現してみましょう。

挨拶文をつくる(役職){

 現在時刻 = 時計の現在時刻

 もし、6:0012:00だったら{
  挨拶文 = おはよう
 }

 もし、部長で12:0015:00もしくは、一般社員で12:0016:00だったら{
  挨拶文 = こんにちは
 }

 もし、部長で15:006:00もしくは、一般社員で16:006:00だったら{
  挨拶文 = こんばんは
 }
}

// 進捗確認は同じなので省略

挨拶文をつくる関数を変える必要が出てきました。
役職を渡して、部長の場合のみ12:00〜15:00をこんにちはに変えました。

オブジェクト指向の場合はどうなるでしょうか。

部長テンプレート (人間テンプレートがベース){

 進捗確認(部下){
  // 同じのため省略
 }

 挨拶する(相手の名前){
  現在時刻 = 時計を見る()

  もし、6:0012:00だったら{
   挨拶文 = おはよう
  }

  もし、12:0015:00だったら{
   挨拶文 = こんにちは
  }

  もし、15:006:00だったら{
   挨拶文 = こんばんは
  }

  *挨拶する相手の名前*さん、*挨拶文*! 
 }
}

部長テンプレートの挨拶の条件式を16:00→15:00に直しました。

「挨拶する」機能は人間テンプレートから引き継いだ機能なので、部長/一般社員テンプレートと共に明記されてませんが使えます。

そして、引き継いだ機能の中身は上書きできます。上書きしても影響はそのオブジェクトに留まります。この例で行けば部長だけが15:00までこんにちはとなり、一般社員は変わらず16:00までです。

この上書きする行為をオーバーライドと呼び、上書きによって同じ機能(メソッド)を呼び出しても違う結果になる現象を、多様性(ポリモーフィズム)と呼びます。

まとめ

いかがだったでしょうか?
長くなりましたが、一通りオブジェクト指向の概念を説明してきました。

いきなり、プログラミングにおけるオブジェクトに入らず、まずは自然言語としてのオブジェクトを紐解く意識をしました。

大事なのはプログラマーを主体として、属性と機能を持ったオブジェクトを作ること。
そして、それらを間接的に使用してシステムを構築すること。
このあたりが染み付くと使いこなせると思います。

この記事が気に入ったらサポートをしてみませんか?