カプセル化

カプセル化の意味

オブジェクト指向では「カプセル化」という表現がよく出てきます。
カプセル化ってどういうことでしょうか。

要は「隠すべきものは隠す」という事です。

フィールドやメソッドなどのクラスのメンバは、修飾子により、アクセス可否がコントロールできます。アクセス可否どころか、クラス定義のコードを直接見ない限り、privateなメンバは存在にすら気付きません。実はここが重要なのです。クラスとして勝手に変えられては困る変数とか、中間処理的な関数とかそういうのは隠すのです。

カプセル化はクラスを設計する上で最も重要なポイントです。
これがなけりゃオブジェクト指向のメリットは生かせません。

カプセル化による恩恵は、

  • 保守性の向上
  • 外部から見たクラスの単純化
  • クラスの持つ性質の正当性を保証

といったところです。
詳しく見てみましょう。

保守性を意識する

カプセル化の基本として、フィールドは直接参照させないということが言えます。
まずは下の例を見てみてください。

フィールドをpublicにした場合
<?php

class Product
{
    public $name;
}

$prd = new Product();
$prd->name = 'かまぼこ';

echo $prd->name . 'と命名した。';
echo $prd->name . 'のラベルに「' . $prd->name . '」と書いた';

?>
フィールドをprivateにした場合
<?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つの例でどんなことになるかを見てみます。
太字の部分が修正箇所です。

フィールドをpublicにした場合
<?php

class Product
{
    public $name;
}

$prd = new Product();
$prd->name = 'かまぼこ';


$name = '名無し';
if ($prd->name != '') {
    $name = $prd->name;
}

echo $name . 'と命名した。';
echo $name . 'は、「僕は' . $name . 'です。」と言った';

?>
フィールドをprivateにした場合
<?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フィールド+セッタ・ゲッタメソッドという構図にたどり着きます。

Productクラスの性質として、$priceは数値である必要があります。でもpublic $priceとやってしまうと、どうやっても$priceに数値以外が入る可能性はなくせません。そこでprivate $priceにして、$ageの値を変化させるにはsetPriceメソッドを通すしかないようにし、さらにsetPriceメソッドには入力値が数値であるかをチェックする機能を持たせる。これで$priceは数値であることが保証されるわけです。

PHPでは型の制約がないため、Javaなんかの言語に比べてもpublicフィールドの危険性は遥かに大きいといえます。

<?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円です。
価格の設定値が不正です。

$price自体は外からは存在すら知らなくてよく、また、setPriceメソッドで何が行なわれているかも知る必要はありません
仮にこのProductクラスの設計者と、Productクラスを利用してアプリ開発を行う開発者が別であったとしたなら、Productクラスの設計者は、Productクラスの使い方のマニュアルを作成し、setPriceを使えば価格をセットできますよ、って書いてあるという情報だけを提供すれば良いのです。Productクラスを利用する人にとっては中で何が行なわれているかは知る必要もなく、マニュアルを信じるだけです。

これが「カプセル化」「隠蔽」の本質と言えると思います。

外部に不必要なメソッドは隠す

クラスは、クラスを利用して実装を行うアプリ開発者にとって出来る限り単純であることが望ましいといえます。一人で全て実装する場合は関係ないと思うかもしれませんが、大規模になるとクラスの定義全ての隅から隅まで永久に頭に入れておくのは難しいことです。

メソッド名から処理内容が推測できるなんてのは言うまでもないことですが、何のために存在するか分からないメソッドなんかは出来るだけなくすようにすべきです。

また、クラスを利用して実装する側から見ると、そのクラスにはどんなメンバが存在し、それがどんな機能を提供するか、という事だけを考えていればよいような形にするべきです。つまりpublicの有用なメソッドのみを網羅したクラスマニュアルでもあればベストです。
その機能がどのような方法で実現されているかとかいうような事は考えたくありません。
例えばPHPの公式マニュアルでも、関数マニュアルはありますが、関数の中で一体何が行なわれているかは書かれていませんし、通常、マニュアルを見る人はそれを知りたくもありません。

そこで、公開の必要のないメソッドは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では、言語仕様として読み取り専用にする仕組みはありません。

それではどうするか。
もうお気づきかもしれませんが、フィールドを読み取り専用にしたい場合、セッタメソッドを作らず、ゲッタメソッドだけ作ります。

<?php

class Product
{
    private $productDate;

    public function __construct($date)
    {
        // 製造日はコンストラクタでのみ設定
        $this->productDate = $date;
    }

     // ゲッタのみ定義し、セッタは設けない
    public function getProductDate()
    {
        return $this->productDate;
    }
}


?>

製造日は勝手に変えられては困るので、値の設定はコンストラクタでのみ行うこととし、ゲッタのみ定義しました。
これで、$productDateフィールドはどうやっても外部から変更することはできません。