PHPオブジェクト指向基礎
カプセル化
カプセル化の意味
オブジェクト指向では「カプセル化」という表現がよく出てきます。
カプセル化ってどういうことでしょうか。
要は「隠すべきものは隠す」という事です。
フィールドやメソッドなどのクラスのメンバは、修飾子により、アクセス可否がコントロールできます。クラスとして勝手に変えられては困る変数とか、中間処理的な関数とかそういうのは隠すのです。
カプセル化はクラスを設計する上で最も重要なポイントです。
これがなけりゃオブジェクト指向のメリットは生かせません。
カプセル化による恩恵は、
- 保守性の向上
- 外部から見たクラスの単純化
といったところです。
詳しく見てみましょう。
保守性を意識する
カプセル化の最も基本として、フィールドは直接参照させないということが言えます。
まずは下の例を見てみてください。
<?php class Product { public $name; } $prd = new Product(); $prd->name = 'かまぼこ'; echo $prd->name . 'と命名した。'; echo $prd->name . 'のラベルに「' . $prd->name . '」と書いた'; ?>
<?php class Product { private $name; public function setName($name) { $this->name $name; } public function getName() { return $this->name; } } $prd = new Product(); $prd->setName('かまぼこ'); echo $prd->getName() . 'と命名した。'; echo $prd->getName() . 'のラベルに「' . $prd->getName() . '」と書いた'; ?>
商品クラスを作り、名前フィールドを定義しました。
フィールドをpublicにした場合と、privateにしてセッタ・ゲッタのメソッドを作った場合の2パターンのクラスです。
両方、同じ事をやっています。
ある日、名前の取得時に、もし名前が未設定だったら「名無し」という名前を表示するという決まりが出来たとします。
当然プログラムの修正が必要になりますが、2つの例でどんなことになるかを見てみます。
太字の部分が修正箇所です。
<?php class Product { public $name; } $prd = new Product(); $prd->name = 'かまぼこ'; $name = '名無し'; if ($prd->name != '') { $name = $prd->name; } echo $name . 'と命名した。'; echo $name . 'は、「僕は' . $name . 'です。」と言った'; ?>
<?php class Product { private $name; public function setName($name) { $this->name $name; } public function getName() { if ($this->name == '') { return '名無し'; } else { return $this->name; } } } $prd = new Product(); $prd->setName('かまぼこ'); echo $prd->getName() . 'と命名した。'; echo $prd->getName() . 'のラベルに「' . $prd->getName() . '」と書いた'; ?>
この二つの例で大きく異なる点は、直接publicフィールドを参照している前者はクラスを使用しているアプリケーション側のプログラムを修正しているのに対し、メソッドを通してフィールドにアクセスしている後者はクラス内のみの修正となっています。
前者の場合、publicフィールドを参照している箇所を全て洗い出し、その全てに対してプログラムの修正が必要になってしまいます。これって大変な労力です。どこでどのようにこのpublicフィールドを直接参照しているか想像もつきませんから。
それで、もし修正漏れがあったら重大なバグにつながる可能性もあります。
それに対して後者の場合、メソッドを介しているため、このメソッド内で必要な処理を行えば、クラスを使用しているアプリケーション側には一切修正は必要なく、クラスのただ一箇所を修正するだけでよくなります。
このようにpublicフィールドを直接参照してしまうと、クラスと外部とが直接依存してしまう関係になり、修正に対して弱い構造となってしまいます。
クラス設計において大事な事は、外部との結びつきを弱めるという事です。
このフィールドにアクセスしたい場合はこのメソッドを通してくださいね、って言うわけです。もっと言えば、フィールドの存在自体、外部は知る必要もないのです。
publicフィールドの危険性
PHPは型の制約がありません。一つの変数に対してどんな型でも入ります。
これがPHPのとっつきやすさに直接つながっていることは言うまでもありません。
しかしこれが危険な事であるという事は、厳密に型の制約のある言語を経験した事のある人はお分かりかと思います。型を意識しないばかりにバグになっているパターンっていうのは結構あったりします。
本題ですが、クラスのフィールドは紛れもなく変数です。そして前述の通りPHPの変数には型の制約はありません。しかし、特にクラスのフィールドの場合、何かの意味を持って定義されているはずです。何かの役割を荷っているはずです。
言ってみればクラスのフィールドは、普通の変数よりも型を意識するべき変数なのです。
例えばpublicフィールドでいたずらをします。
<?php class Product { public $price; } $prd = new Product(); $prd->price = 100; echo $prd->price . '円です。
'; $prd->price = 'バカ'; echo $prd->price . '円です。
'; ?>
100円です。 バカ円です。
とんでもない事になっています。
もし型の制約があるなら、$ageを数値型で定義すればこんな事にはなりませんが、PHPではそうは行きません。
なのでチェックロジックをはさまなければなりません。そのためにはメソッドをはさむ必要があります。すると結局、privateフィールド+セッタ・ゲッタメソッドという構図にたどり着きます。
<?php class Product { private $price; // ゲッタ public function getPrice() { return $this->price; } // セッタ public function setPrice($price) { if (false == is_numeric($price)) { throw new Exception('価格の設定値が不正です'); } $this->price= $price; } } try { $prd = new Product(); $prd->setPrice(100); echo $prd->getPrice() . '円です。'; $prd->setPrice('バカ'); echo $prd->getPrice() . '円です。'; } catch (Exception $e) { echo $e->getMessage(); } ?>
10円です。 価格の設定値が不正です。
PHPでは型の制約がないため、Javaなんかの言語に比べてもpublicフィールドの危険性は遥かに大きいといえます。
外部に不必要なメソッドは隠す
クラスは、クラス使用者にとって出来る限り単純であることが望ましいといえます。
メソッド名から処理内容が推測できるなんてのは言うまでもないことですが、何のために存在するか分からないメソッドなんかは出来るだけなくすようにすべきです。
また、クラスを使用する側から見ると、そのクラスにはどんなメンバが存在し、それがどんな機能を提供するか、という事だけを考えていればよいような形にするべきです。
その機能がどのような方法で実現されているかとかいうような事は考えたくありません。
そこで、公開の必要のないメソッドはprivateにしてしまいます。
<?php class Product { private $price; public function __construct($price) { if (false == $this->checkPrice($price)) { throw new Exception('価格の設定値が不正です'); } $this->price = $price; } public function adjustPrice($price) { if (false == $this->checkPrice($price)) { throw new Exception('価格の設定値が不正です'); } $this->price += $price; } private function checkPrice($count) { if (false == is_numeric($count)) { return false; } return true; } } ?>
この例では、コンストラクタで価格の初期値を設定し、その後、adjustPriceメソッドで価格の調整が出来るようになっています。
価格は当然数値なので、設定しようとする値が数値であるかのチェックを行います。
このチェックをコンストラクタとadjustPriceメソッドの両方で行うため、チェックメソッドを作成し、チェックの必要な箇所から呼び出すような形にしました。
このチェックメソッドは完全に内部処理用のものなのでpublicにする必要はありません。しても問題があるわけではありませんが、クラスの使用者からすれば、これは何のためのメソッドなのだろうかと思い悩んでしまうかもしれません。
なのでprivateにします。
メソッドの公開は必要最低限でいいのです。
読み取り専用フィールド
外部から書き換えられては困るようなフィールドがある場合、読取専用にする必要があります。
容易に想像がつくと思いますが、
フィールドを読み取り専用にしたい場合、セッタメソッドを作らず、ゲッタメソッドだけ作ります。
<?php class Product { private $productDate; public function __construct($date) { // 製造日はコンストラクタでのみ設定 $this->productDate = $date; } // ゲッタのみ定義し、セッタは設けない public function getProductDate() { return $this->productDate; } } ?>
製造日は勝手に変えられては困るので、値の設定はコンストラクタでのみ行うこととし、ゲッタのみ定義しました。
これで、$productDateフィールドはどうやっても外部から変更することはできません。

コンストラクタ

