起動するメソッドがないとき

今回の状況

本日、仕事でPHPを用いていて、次のような状態となっていました。

  • ItemBaseクラス…全てのアイテムのスーパークラス
  • ItemA…あるアイテム(独自のメソッドを持つ)
  • ItemB…あるアイテム(独自のメソッドを持つ)
  • ForbiddenItem…アイテムへのアクセスが禁止されている時に用いられるダミークラス
  • NotFoundItem…アイテムが見つからなかったときに用いられるダミークラス
<?php
class ItemBase{
   
}

class ItemA extends ItemBase{
   function getXXX(){
      ...
   }
}

function ItemB extends ItemBase{
   function getABC(){
      ...
   }
}

function ForbiddenItem extends ItemBase{
   function getString(){
      return "アクセス禁止";
   }
}

function NotFoundItem extends ItemBase{
   function getString(){
      return "見つかりません";
   }
}
?>

という感じです。
使い方は次のような感じ。

<?php

$itema = getItemA(.....); // ItemAオブジェクトを取得する
$itemb = getItemB(.....); // ItemBオブジェクトを取得する

print $itema->getXXX();
print $itemb->getABC();
?>

きちんとそのオブジェクトが帰ってくるとは限らない

さて、getItemA()やgetItemB()は、本来はItemAオブジェクトやItemBオブジェクトを返してくるわけなのですが、それらのオブジェクトが見つからなかった、あるいは別個に実装されている権限設定によってアクセスできなかったときにはNotFoundItemオブジェクトやForbiddenItemオブジェクトが帰ってきます。

しかし、それらのクラスではgetXXX()やgetABC()は実装されていません。もちろん、実装してもよいのですが、そうすると、ItemCを作って別のメソッドを実装すると、それもまたNotFoundItemやForbiddenItemで実装せねばなりません。

そのような状況でどうするべきか。

メソッド呼出をオーバーロード

私は、次のような指示を行っておきました。

ForbiddenItemに対して、getXXX()を呼び出したときにそのメソッドが実装されていなかったら、現在ではエラーになる。

しかし、メソッドを呼び出すというのは、(1)メソッドを見つけて(2)そのメソッドに対して呼び出しを行う という二段階となっているはずであり、その(1)の部分を「見つからなくても他のメソッドで置き換えて見つかったことにする」というもので差し替えるという手段があるはず。それを使え。

というものです。Rubyでいうところのmethod_missing()ですが、動的な型の言語にはやはり必要なのではないかとかねがね思っています。

その方法を探してもらったのですが(いや、結局私が探したのですが)、PHPにおいてはオーバーロードというテクニックだそうです。

<?php
class ForbiddenItem extends ItemBase{
   // メソッド呼び出しの置き換え
   function __call($name, $args, &$return){
      // $fitem->abcd(1,2,3);
      // ならば、$name="abcd", $args = array(1,2,3)となり
      // $return に格納した値が返り値となる。
      // なお、__call()でfalseを返すと、メソッドが見つからなかった扱いとなる
      $return = $this->getString();
      return true;
   }
   function getString(){
      return "アクセス禁止";
   }
   // ここでは書かないが、__get()でプロパティ呼び出しの置き換え
   // __set()でメソッドの動的な追加ができるそうな。
}
overload('ForbiddenItem'); // PHP4

?>

これにより、ForbiddenItemに対する未知のメソッドの呼び出しは全てgetString()に転送されることになります。

この方法は正しいか?

私は、めんどくさがりという、プログラマの美徳を持っている人間ですので、思わず上のような方法を見つけてきます。しかし、本当に正しいのか?

ただでさえ、動的な型によっていい加減になってしまっているコードがさらにいい加減になるわけで、「指示は間違っていたかもしれないな」と夜になって少し反省しています。

ま、みんなが使えば怖くない、という話もありますので、広く使われていれば「みんな使ってますよ」とごまかせます。そういう意図を込めつつ、この手法を紹介してみました。