通勤電車の中で思いついたコードを、昼休みを利用して書いてみました。動いたので公開。ただし、バッドノウハウなので要注意。

解説

まず、クロージャを利用して、外からは見えない privateNameSpace という名前空間を作ります。この privateNameSpace 自体は、言わば private static なものなので、Dog オブジェクトが個別に持つものではなく、Dog クラスに紐づく (ように見える) 空間です。

これを、各インスタンス毎に分けて使えるよう、Dog コンストラクタで objectId というメンバ (これは public) を作っています。objectId は、ミリ秒単位の時間と、乱数を文字列として結合したもので、およそユニークであることが期待されています。とはいえ、理論上は衝突の可能性もあり得るのですが・・・。

この objectId をキーに、privateNameSpace の中に、連想配列オブジェクトを作れば、インスタンスごとに private な名前空間を持つことができる、というわけです。

2 番目のコードを動かしてみると、何となくそれらしく動いていることが確認できます。

ただ、大きな問題は、objectId を乗っ取ることができてしまうということです。3 番目のコードのようにすることで、もともとは pochi オブジェクトだったものが、あたかも taro オブジェクトであるかのように振舞うようになってしまいます。

まとめ

上記の問題の他にも、メンバの取得のための記述が煩雑なところも大きな問題と言えます。もう少し抽象化できれば書きやすくなるかもしれませんが、いずれにせよ、実用的な方法では無いでしょう。

似たようなことを実現するためのノウハウはネット上にいろいろ転がっているようなので、そちらも読んでみたいと思った次第です。

あとで読む

,

2010-03-07 15:30 追記
Observer Pattern について調べ直してみたところ、一般的な Obsesrver Pattern と、以下のコードは似て非なるものであることに気づきました。
このエントリは GoF パターンの教材にはなり得ないので、その点に注意してお読みください。

入浴中にふと思いつき、書いてみたコードを紹介。

Observer Pattern については詳しく紹介しないので、興味のある方は Wikipedia の Observer パターンの頁をご覧ください。かくいう私自身も、いわゆる GoF パターンとしての Observer Pattern を暗記しているわけではないのですが、Observer Pattern 的にはなっていると思います。

Notifier.php

監視者にメッセージを送る通知者クラス。

class Notifier
{
    private $_observers = array();

    public function addObserver($observer)
    {
        $this->_observers[] = $observer;
        return $this;
    }

    public function __call($methodName, $args)
    {
        foreach ($this->_observers as $observer)
        {
            call_user_func_array(array($observer, $methodName), $args);
        }
        return $this;
    }
}

そして、次に監視者クラスを 2 つほど定義。
まずは文字列をテキストファイルに書き込んでいくロガー。

Logger.php

class Logger
{
    private $_fp;

    public function __construct($fileName)
    {
        $this->_fp = fopen($fileName, 'a');
    }

    public function __destruct()
    {
        fclose($this->_fp);
    }

    public function putString($str)
    {
        $this->_log($str);
    }

    public function putArray($arr)
    {
        foreach ($arr as $str)
        {
            $this->_log($str);
        }
    }

    private function _log($str)
    {
        fputs($this->_fp, date('Y-m-d H:i:s') . "\t" . $str . "\n");
    }
}

次は、顔文字の吹き出しとして、文字列を出力するもの。

Speaker.php

class Speaker
{
    private $_face;

    public function __construct($face)
    {
        $this->_face = $face;
    }

    public function putString($str)
    {
        $this->_speak($str);
    }

    public function putArray($arr)
    {
        foreach ($arr as $str)
        {
            $this->_speak($str);
        }
    }

    public function _speak($str)
    {
        echo $this->_face . ' < ' . $str . "\n";
    }
}

そして、実際に実行するのは以下のコード。

main.php

require_once './Notifier.php';

require_once './Logger.php';
require_once './Speaker.php';

// 通知者オブジェクトの生成
$notifier = new Notifier;
// 通知者に、監視者オブジェクトを登録する
$notifier->addObserver(new Logger('./log.txt'));
$notifier->addObserver(new Speaker('( `・ω・´)'));

$str = "Pentagram / Relentless (1985)";
$arr = array(
    "01. Death Row",
    "02. All Your Sins",
    "03. Sign of the Wolf (Pentagram)",
    "04. The Ghoul",
    "05. Relentless",
    "06. Run My Course",
    "07. Sinister",
    "08. The Deist",
    "09. You're Lost I'm Free",
    "10. Dying World",
    "11. 20 Buck Spin"
);

// いろいろなメソッドを実行
$notifier->putString($str);
$notifier->putArray($arr);

これを実行すると、こうなります。

$ php main.php
( `・ω・´) < Pentagram / Relentless (1985)
( `・ω・´) < 01. Death Row
( `・ω・´) < 02. All Your Sins
( `・ω・´) < 03. Sign of the Wolf (Pentagram)
( `・ω・´) < 04. The Ghoul
( `・ω・´) < 05. Relentless
( `・ω・´) < 06. Run My Course
( `・ω・´) < 07. Sinister
( `・ω・´) < 08. The Deist
( `・ω・´) < 09. You're Lost I'm Free
( `・ω・´) < 10. Dying World
( `・ω・´) < 11. 20 Buck Spin

そして、同時に、以下のようなログファイルまで出力されます。

2010-03-06 22:42:09     Pentagram / Relentless (1985)
2010-03-06 22:42:09     01. Death Row
2010-03-06 22:42:09     02. All Your Sins
2010-03-06 22:42:09     03. Sign of the Wolf (Pentagram)
2010-03-06 22:42:09     04. The Ghoul
2010-03-06 22:42:09     05. Relentless
2010-03-06 22:42:09     06. Run My Course
2010-03-06 22:42:09     07. Sinister
2010-03-06 22:42:09     08. The Deist
2010-03-06 22:42:09     09. You're Lost I'm Free
2010-03-06 22:42:09     10. Dying World
2010-03-06 22:42:09     11. 20 Buck Spin

これがどう嬉しいのか。Observer Pattern の例としてよくあるのは、通知者オブジェクトの notify メソッドが呼び出されると、監視者オブジェクトたちの update メソッドを実行していく、というパターン。対する、この Notifier オブジェクトは、notify 的メソッドをいくつでも追加できるという特性を持ちます。いわば、動的 Observer Pattern

putString メソッドも、putArray メソッドも、Notifier は知りませんが、自分が知らないメソッドを受けとると、監視者オブジェクトたちに「putString してください」「putArray してください」というメッセージを、引数を伴って通知して回るのです。

これを実現するのが、以前のメタプログラミングに関する記事で紹介した、__call というマジックメソッド。以前の繰り返しになりますが説明すると、実行しようとしたメソッドがなかったときの転送先のメソッドです。引数には、実行しようとしたメソッド名と、そのメソッドに渡された引数を受けます。

ところで Ruby には Observable という、Observer Pattern を実現するためのモジュールがあります。非常に動的な特性を持つ Ruby のことですから、上記の Notifier と同じような仕様になっているのではと思い、調べてみたところ、notify されたら update していくだけの普通のものでした。つまり、静的 Observer Pattern

これは我ながらおもしろいアイディアではないかなと思っているのですが、やはり、PHP よりはむしろ Ruby のほうがしっくりパターンかもしれません。method_missing メソッドを使えば、簡単に同じようなことが実現できると思います。

, ,

昨日投稿したメタプログラミングで PHP をもっとオブジェクト指向にだと、以下のようにしか書けませんでした。

$obj = new MyObject(array('foo', 'bar', 'baz'));
echo $obj->join(',')->strtoupper();
// => FOO,BAR,BAZ

どういうことかというと、new の直後でメソッドを呼び出すことができないようで、上記のように 2 行で書かざるを得なくなっていました。

そこで、MyObject クラスを以下のように書き換えました。

class MyObject
{
    private $_selfMember;

    public function __construct($selfMember)
    {
        $this->_selfMember = $selfMember;
    }

    public static function init($selfMember)
    {
        return new self($selfMember);
    }

    public function __call($name, $args)
    {
        $new_args = array_merge(
            array($this->_getSelfMember()),
            $args
        );
        return new self(call_user_func_array($name, $new_args));
    }

    public function _getSelfMember()
    {
        return $this->_selfMember;
    }

    public function __toString()
    {
        return print_r($this->_selfMember, true);
    }
}

変更点は、init というスタティックメソッドを追加しただけです。

この変更により、以下のように書けるようになりました。

echo MyObject::init(array('foo', 'bar', 'baz'))->join(',')->strtoupper();
// => FOO,BAR,BAZ

楽しいような、危なっかしいような感じですが、言いたいこととしては、マジックメソッドを使うといろいろおもしろいことができそうですよ!ということにさせてください。こういう、いかにも役に立たなそう (というかむしろ害悪になりそう) なコードでも、遊びでいろいろ試しているうちに、実用に耐えうるアイディアに化けるかもしれません・・・。

, ,