プログラミング勉強中の人にオブジェクト指向とは何なのかを何となく伝えたい話
この文章について
OOP(オブジェクト指向プログラミング、オブジェクト指向パラダイム)について
プログラミング勉強中の大学生さんに説明する機会が何度かあったので、
自分の中で整理するために書きました。
中には適切でない説明もあります。ばっさり省いているところもあります。
詳細より イメージを掴んでもらうことを優先しているためです。
「それにしてもあんまりだなー」という表現がありましたらご連絡いただけると嬉しいです。
大学生さん
大学生さんたちはいろんな背景を持っています。
- プログラミングを始めたばかりの人
- 独学で Objective-C や JavaScript を書いた経験がある人
- Web やコンピュータの仕組みについてもこれから勉強する予定の人
使用言語
大学生さんたちはプログラミングの第一歩として JavaScript と PHP を使っています。ここでは説明に PHP のコードを使います。
PHP はクラスベースのオブジェクト指向を採用している言語です。
説明したいポイント
オブジェクト指向とはどんなものか
なぜオブジェクト指向を適用するのか
オブジェクト指向プログラミングに関するキーワード
オブジェクトに関するざっくりとした説明
コードを書いていると以下のような構文に出会います。
new
って何でしょう。
変数から出ている ->
の記号も気になります。
$user = new User(); $user->setName("nekoko"); echo $user->name;
ざっくり説明すると、 new User
の User
部分はクラス(定義)です。
new
という命令により、定義から実体を作っています。
上記のコードでは $user
にはその実体が格納されています。
また、クラスにはプロパティやメソッドが定義されています。
実体の持つプロパティやメソッドへアクセスするために使われる記号が ->
です。
まったくわけが分からない説明ですね(´・ω・`)
オブジェクト?
オブジェクトとはデータと処理の集まりです。
漠然とした集まりではなく、ひとつのテーマを持った集まりです。
上記のコード例では User
というものが出てきました。
一般的にユーザ情報にはどんなデータが含まれるでしょうか。
「名前」や「住所」、「電話番号」を持っているかもしれません。
それぞれが保持しているデータを状態と表現することもあります。
オブジェクトはこれらのデータ(状態)の他に振る舞いも持ちます。
振る舞いは自分自身に対する操作です。 オブジェクトに対して「名前を教えて」と伝えたときにオブジェクト自身が保持している名前を返す、などの動作を指します。
たとえば「鈴木さん」と「高橋さん」というユーザがいるとします。
この場合「鈴木さん」と「高橋さん」は別々の異なるオブジェクトです。
鈴木さんは「名前が鈴木である」という状態を保持していて、「名前を教えて」と問えば「鈴木」と答える振る舞いを持っています。
プロパティとメソッド
上の方で「クラスにはプロパティやメソッドが定義されている」と書きました。
プロパティはオブジェクトが保持しているデータ、状態、変数、
メソッドはオブジェクト自身に対する操作、振る舞い、関数のことです。
OOP におけるオブジェクトを構成するのは基本的にこのふたつです。
クラス?
クラスベースにおいて、クラスはオブジェクトの設計書(定義)です。
実体?
コンピュータ的には、メモリを確保した状態を指します。
実体のことをインスタンスと呼びます。
また、実体化することをインスタンス化といいます。
クラスとインスタンスの関係
家を建てる設計図がクラスで、設計図を元に建てた家がインスタンス。
たい焼きの型がクラスで、型を使って焼いたたい焼きがインスタンス。
家を建てるための設計図があるとします。
設計図を元に、同じ形の家をたくさん建てることができます。
設計図がただそこにあるだけでは家はできあがりません。
設計図を元に大工さんがトンカンすると家が建ちます。
設計図に書いてあるドアを開けることはできませんが、実際に建てた家のドアを開けることはできます。
たい焼きの型を元に、同じ形のたい焼きをたくさん作ることができます。
たい焼きの型だけではお腹は膨れません。
型に生地を流し込んで焼くことで食べることができます。
型にはあんこが含まれませんが、実際に焼いたたい焼きにはあんこが含まれるかもしれません。
プログラミングにおける実体化
プログラミングにおけるクラスも似たようなイメージです。
クラス(定義や設計図)を元に、実体化させる必要があります。
PHP では実体化のために new
キーワードを用います。
また、実体化することでインスタンスが持つプロパティやメソッドへアクセスできます。
実装例
たい焼きクラスを実装しています。
たい焼きは内部にあんこ $anko
を保持しています。
また、あんこの種類を答えるメソッド getAnko()
を持っています。
(たい焼き自身が、中にどんなあんこが入っているかを知っている)
class Taiyaki { // 内部にあんこを保持するためのプロパティ(変数)です // 定義の段階ではあんこを持っていませんし、どんなあんこが入るかも分かりません private $anko; // コンストラクタ // new したときに呼び出されるメソッドです // 引数としてあんこを受け取り、プロパティに入れています public function __construct($anko) { $this->anko = $anko; } public function getAnko() { return $this->anko; } } // たい焼きを実体化 `new` する段階で、引数としてあんこを渡しています // 各たい焼きにはそれぞれ異なるあんこを入れています $tsubuanTaiyaki = new Taiyaki('tsubuan'); $creamTaiyaki = new Taiyaki('cream'); $zundaTaiyaki = new Taiyaki('zunda'); // たい焼き自身にメッセージを送り、振る舞いを引き出してみます // それぞれのたい焼きが持つあんこの種類が返ってきます echo $tsubuanTaiyaki->getAnko(); #=> "tsubuan" echo $creamTaiyaki->getAnko(); #=> "cream" echo $zundaTaiyaki->getAnko(); #=> "zunda"
実体化は必須なのか
長々とここまで書いたけど、実体化は必須ではありません。
実体化させなくても、クラス内のプロパティやメソッドに直接アクセスさせることもできます。
インスタンスではなく、クラスに属するプロパティやメソッドもあるのです…。
このようなプロパティやメソッドは静的だと言われます。
クラスに含まれるプロパティやメソッドを静的に定義することもできますし、言語によってはクラスそのものを静的であると定義できます。
PHP では static
キーワードを使うことで、静的なプロパティやメソッドを実装できます。
なぜオブジェクト指向なのか
OOP という考え方があるということ、言語でどのように表現されるかについてはお話ししました。
ではなぜこのような考え方が必要なのでしょうか。
お任せするということ
負担を減らす
生徒がたくさんいる学校を想像してみてください。
いくつかの部活動があるとします。
生徒はそれぞれどこかの部に所属しています。
部活動の時間になりました。
教師が生徒一人一人の部活動を調べて「あなたは野球部だからユニフォームに着替えて校庭へ行きなさい」「あなたは吹奏楽部だから音楽室へ行きなさい」と指示をするのは大変ですね。
生徒に「部活動の時間ですよ」って伝えたとき、それぞれが自身の部活動に合った行動をとってくれれば教師の負担は軽くなります。
処理を複雑にさせない
プログラムでも同じことが言えます。
メインの処理がそれぞれのデータに対して細かく指示を出していると、処理が複雑になる傾向にあるのです。
上記の例を OOP に当てはめると生徒はオブジェクトであり、教師はそれらのオブジェクトを扱う処理に近いかもしれません。
複雑なプログラムはバグを生み出しやすく、また変更が難しくなります。
プログラムには変更されることが多いものです。変更に強い作りにすることで修正作業が楽になり、バグの発生を抑えやすくなります。
カプセル化
オブジェクトにお任せした以上、オブジェクト自身以外の誰かがオブジェクトに干渉しすぎてもよくないですよね。
プロパティ(状態)の変更についてはオブジェクト自身に任せた方がよい(任せるべき)という場合もあります。オブジェクトの外側から状態を操作できないようにするのです。
これを隠蔽、またはカプセル化と呼びます。
PHP では private
キーワードを使用します。
継承
同じような性質を持つオブジェクトが複数あったとき、同じ定義を何回も書くのは冗長ですね。似たようなオブジェクトをまとめられると楽そうです。
継承を使うことで実現できます。
実装例
たとえば部活動を表すオブジェクトがあったとします。
class BaseballClub { private $place = "ground"; // 活動場所 public function getPlace() { return $place; } } class BrassBand { private $place = "music-room"; public function getPlace() { return $place; } }
野球部と吹奏楽部はプロパティとメソッドを持っています。
他の部活動も同じような属性を持つとします。
PHP での継承には extends
キーワードを使用します。
class Club { private $place; public function getPlace() { return $place; } } class BaseballClub extends Club { private $place = "ground"; } class BrassBand extends Club { private $place = "music-room"; } $club = new BaseballClub(); echo $club->getPlace(); #=> "ground" $club = new BrassBand(); echo $club->getPlace(); #=> "music-room"
BaseballClub クラス、BrassBand クラスには getPlace メソッドが定義されていません。でもそれぞれのインスタンスから呼び出すことができています。
これらのクラスが Club クラスを継承しているためです。
オブジェクト指向のポイント
OOP を適用する際に気にしたいポイントについて。
この辺の話は OOP に限らず、ソフトウェア開発全般に言えることです。
凝集度と結合度
凝集度とはオブジェクトやモジュールが、自身のすべき処理(責任)にどれだけ集中しているかという尺度です。
自身が担うべきではない処理を含んでいたり、また責任がはっきりしない処理を含む場合、「凝集度が低い」と表現されます。
また、オブジェクトやモジュール、関数同士の結びつきを結合度と呼びます。
処理同士の結合度は低い方がよいとされています。
処理同士の結合度が高い場合、一方の処理を変更した際にもう一方に影響が出る可能性が高くなります。一箇所修正しただけなのに複数箇所を修正しなくちゃいけないなんてめんどくさいですよね。
凝集度と結合度は一緒に語られることも多いです。
処理同士の結合度が低い=各処理の凝集度が高いと言える場合も多いです。
概念、仕様、実装のレイヤ
ソフトウェア開発の考え方に、以下のようなものがあります。
3つの観点から捉えるというものです(これはとても好きな図です)
概念(conceptual)
- 概念を実装するソフトウェアとは切り離されたもの
- 責任
- 「私が何に対して責任があるのか?」
仕様(specification)
- ソフトウェアのインタフェース
- 振る舞い
- 「私はどのように使用されるのか?」
実装(implementation)
- 具体的な処理
- ソースコード、データ、演算処理
- 「私はどのようにして自信の責任を全うするのか?」
継承はどこで使うべきか
継承はオブジェクトを分類するための手段です。
「同じプロパティやメソッドを持っている」という実装寄りの理由で継承を使うべきではありません。
継承を使うべきかどうかを判断するときは実装ではなく、概念や仕様に着目します。同じ概念を持つクラスをまとめるために継承を使用するとよいです。
オブジェクト指向は万能ではない
OOP は現在のソフトウェア開発でも広く使われている考え方ですが、絶対的なものでもないですし、万能でもありません。
要件によっては別の考え方が適している場合もあります。
OOP を無理矢理適用することで、逆にシステムが複雑になることもあり得ます。
たくさんある考え方の中のひとつとして、OOP のことも知っておくといいんじゃないかなーと思います。
伝えるためにどうしたらよいか
ということを伝えるために、どうしたらよいのか考え中です。
オブジェクト指向の分かりにくさは一体何なのか
「OOP は説明を読んでもよく分からない」という話をよく耳にします。
分からないポイントみたいなものがあるんじゃないかなーと思っています。
自分が特に思うのは以下の二点。
- 用語
- なぜ必要なのか
1 ですが、人や文章によって「オブジェクト」という言葉の指すものが異なる場合があります。概念であったり、インスタンスであったり、定義であったり。
この辺が混ざってしまうと、説明を読んだり聞いている側は混乱するんじゃないでしょうか(というか自分が昔混乱してた)
さらに最初の頃に突然「クラス」や「インスタンス」と言われても何が何だか…
2 については何事においてもそうだと思うのですが、OOP に関してもやっぱり書いていかないと分からない、経験してみないと実感としてわかない部分が多いと思います。(概念を理解するのにコーディングは必須ではないかもしれませんが、一般的に)
設計、処理をクラス化するべきか否か、メソッドの粒度等。
また、思想や世界観を共有するというところから話を始めるのも大事だなーと思うことがあります。
教えるタイミングはいつがよいのか
まだ理解できないだろうから説明しなくていいのかというと、そうとも言い切れないんじゃないかなーと考えています。
理解できなかった概念を、コードを書いている中で理解できる瞬間があるからです。そしてその瞬間はとても気持ちのよいものだと思っています。
なのでプログラミング勉強中の大学生さんに対しても、「一度に理解してもらえるとは思っていないけど説明するよ」って言いながら話すことがあります。
プログラミングを勉強し始めた大学生さんに OOP について教えるときには、上記からかいつまんで説明しています。伝えるポイントの取捨選択はいつも悩むところ。もっと簡潔に、適切に表現できるようになりたい。
分かりやすい説明や体験談があったらぜひ聞いてみたいです。