protectedを乱用してパッケージメンバを作る

Posted by Hiraku on 2012-10-22

PHPではprivateと宣言したプロパティ、メソッドは、同じクラスのインスタンスであれば相互にアクセスできます。あまり意識することはないですが、たまにぎょっとすることになります。

<?php
class Klass {
    private $data;

    function __construct($init) {
        $this->data = $init;
    }

    function get(self $o) {
        return $o->data;
    }
}

$a = new Klass('a instance');
$b = new Klass('b instance');

//echo $a->data; //これはエラーになる
echo $b->get($a); //a instance //$bが$aのprivateメンバを読めた

これはprotectedの場合でも同様であり、しかも継承したクラスにまで影響を及ぼすため、これによってパッケージメンバみたいなものを作ることができます。

パッケージメンバという単語ですが、この記事では「あるライブラリの内部クラスだけが直接アクセスでき、ライブラリの外からはアクセスできないメンバー変数」という意味合いで使っています。

オブジェクト指向としては、継承を変な方向に使うのでダメなパターンだと思いますが、面白いので紹介してみようと思います。

ユースケース

Aクラスはprivate変数$aを持っているとしましょう。$aはAクラスの内部だけで使うことを想定しています。

BクラスはAクラスのインスタンスを使う側だとします。さて、Bクラスが$aにアクセスするにはどうすればいいでしょうか?

class A {
  private $a;
  //...
}

class B {
  function getA(A $obj) {
    //echo $obj->a; //Aクラスのaメンバにアクセスしたい!!
  }
}

当然、このままでは$aにアクセスできません。そこで普通はgetterを作って、読み出しだけならできる状態にして、Bクラスからアクセスできるようにします。

class A {
  private $a;

  function getA() {
    return $this->a;
  }
  //...
}

class B {
  function getA(A $obj) {
    echo $obj->getA(); //Aクラスのaメンバにアクセスできた
  }
}

しかし、「Bクラス限定で公開すれば十分」なのに、getterはpublicなのでありとあらゆる場所からアクセスできてしまいます。権限を広げすぎているのです。

普通は、まあ権限を広げすぎといっても別に使わなければいいし、このままにすることが多いと思いますが、protectedを使うともっと厳密に公開先を限定することができます。

protectedによる変数の相互アクセス

基底クラスを一つ作り、基底クラスにprotectedメンバを作っておくだけです。

先の例ですと、AとBの共通基底クラスとしてBaseクラスを作り、$aはBaseクラスのprotectedメンバとして宣言しておきます。こうすると、Bクラスから$aにアクセスできるようになります。

class Base {
  protected $a;
}

class A extends Base {
}

class B extends Base {
  function getA(A $obj) {
    echo $obj->a; //アクセス可能
  }
}

protectedは継承関係にあるクラスで相互にアクセス可能なので、逆にライブラリを構成するクラスを全て継承関係にしてしまえば、protectedがパッケージメンバ相当になる、ということです。(わかりにくいな。。。)

継承の頂点で宣言されたprotectedメンバはこの三角形の範囲に引き継がれ、しかも相互にアクセス可能になります。継承の関係にないクラスや文脈ではprotectedメンバはアクセスできないため、公開範囲を絞ることができます。

protected.png

図のように、子クラスで宣言されたprotectedは、その子クラスとその更に子クラスでのみ相互アクセス可能であり、三角形の範囲外からはアクセスできません。これを利用すると、サブパッケージのようなものを作ることも可能です。

考察

これでパッケージメンバを作ることはできるのですが、継承を消費せざるを得ないので、実際にこれが使える場面は少ないかもしれません。あと、なぜgetterなしでアクセスできるのかがわかりにくいし。



keyword: OOP

PHPの最新記事