XCVIII. オブジェクトの集約/合成関数

警告

この拡張モジュールは、 実験的 なものです。この拡張モジュールの動作・ 関数名・その他ドキュメントに書かれている事項は、予告なく、将来的な PHP のリリースにおいて変更される可能性があります。 このモジュールは自己責任で使用してください。

導入

オブジェクト指向プログラミングでは、簡単なクラス (または インスタンス) を組み合わせてより複雑なクラスを作成するということが 一般に行われます。これは、複雑なオブジェクトやオブジェクト階層を 構築するための柔軟な方法であり、多重継承と同等のことを動的に行う 機能を有します。 クラス(またはオブジェクト)を合成するには、合成される要素の間の 関係により関連(Association)集約(Aggregation)の 2 種類の方法があります。

関連は、独立に構築され、外部から 可視の部分を合成したものです。クラスまたはオブジェクトを 関連づける場合、各クラスは関連するクラスへのリファレンスを保持します。 複数のクラスを静的に関連づける場合は、クラスは他のクラスの インスタンスへのリファレンスを含みます。例えば、

例 1. クラスの関連付け

<?php
class DateTime {
   
   function
DateTime()
   {
       
// 空のコンストラクタ
   
}

   function
now()
   {
       return
date("Y-m-d H:i:s");
   }
}

class
Report {
   var
$_dt;
   
// その他のプロパティ ...

   
function Report()
   {
       
$this->_dt = new DateTime();
       
// 初期化コード ...
   
}

   function
generateReport()
   {
       
$dateTime = $this->_dt->now();
       
// その他のコード ...
   
}

   
// その他のメソッド ...
}

$rep = new Report();
?>
コンストラクタ (または他のメソッド) にリファレンスを渡すことにより 実行時に複数のインスタンスを関連づけることも可能です。 これにより、複数のオブジェクト間の関連を動的に変更することが 可能です。この例を示すために上の例を変更してみます。

例 2. オブジェクトの関連

<?php
class DateTime {
   
// 上の例と同じ
}

class
DateTimePlus {
   var
$_format;
   
   function
DateTimePlus($format="Y-m-d H:i:s")
   {
       
$this->_format = $format;
   }

   function
now()
   {
       return
date($this->_format);
   }
}

class
Report {
   var
$_dt;    // DateTime へのリファレンスをここに保持
   // その他のプロパティ ...

   
function Report()
   {
       
// 初期化を行う
   
}

   function
setDateTime(&$dt)
   {
       
$this->_dt =& $dt;
   }

   function
generateReport()
   {
       
$dateTime = $this->_dt->now();
       
// その他のコード ...
   
}

   
// その他のメソッド ...
}

$rep = new Report();
$dt = new DateTime();
$dtp = new DateTimePlus("l, F j, Y (h:i:s a, T)");

// Web 表示用に簡単な日付を付けたレポートを生成する
$rep->setDateTime(&$dt);
echo
$rep->generateReport();

// その他のコード ...

// かっこの良いレポートを生成する
$rep->setDateTime(&$dtp);
$output = $rep->generateReport();
// データベースに $output を保存
// ... 等 ...
?>

一方、集約では、合成されたパーツのカプセル化 (隠蔽) が行われます。(静的な) 内部クラス (PHP はまだ内部クラスを サポートしていません) を使用することにより、クラスを集約することが できます。この場合、このクラスを含むクラスを通じる場合以外、集約された クラスの定義にはアクセスできません。複数のインスタンスの集約 (オブジェクト集約) は、あるオブジェクトの内部にサブオブジェクトを 動的に作成することを意味し、この過程でこのオブジェクトのプロパティと メソッドを拡張します。

オブジェクトの集約は、(例えば、分子は原子を集約したものであるといった) 包含関係を表す際の自然な方法であり、サブクラスを複数の親クラス およびそのインターフェイスに永続的にバインドすることなく、 多重継承と等価な機能を得るために使用できます。 実際、オブジェクトの集約はより柔軟に使用することができ、集約される オブジェクトで継承するメソッドまたはプロパティを選択することが できます。

3 つのクラスを定義し、各々に別々のストレージメソッドを実装します。

例 3. storage_classes.inc

<?php
class FileStorage {
    var
$data;

    function
FileStorage($data)
    {
        
$this->data = $data;
    }
    
    function
write($name)
    {
        
$fp = fopen(name, "w");
        
fwrite($fp, $this->data);
        
fclose($data);
    }
}

class
WDDXStorage {
    var
$data;
    var
$version = "1.0";
    var
$_id; // "private" 変数

    
function WDDXStorage($data)
    {
        
$this->data = $data;
        
$this->_id = $this->_genID();
    }

    function
store()
    {
        if (
$this->_id) {
            
$pid = wddx_packet_start($this->_id);
            
wddx_add_vars($pid, "this->data");
            
$packet = wddx_packet_end($pid);
        } else {
            
$packet = wddx_serialize_value($this->data);
        }
        
$dbh = dba_open("varstore", "w", "gdbm");
        
dba_insert(md5(uniqid("", true)), $packet, $dbh);
        
dba_close($dbh);
    }

    
// プライベートメソッド
    
function _genID()
    {
        return
md5(uniqid(rand(), true));
    }
}

class
DBStorage {
    var
$data;
    var
$dbtype = "mysql";

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

    function
save()
    {
        
$dbh = mysql_connect();
        
mysql_select_db("storage", $dbh);
        
$serdata = serialize($this->data);
        
mysql_query("insert into vars ('$serdata',now())", $dbh);
        
mysql_close($dbh);
    }
}

?>

この定義済みクラスを用いていくつかのオブジェクトをインスタンス化し、 集約や集約の解除を行いつつ随時オブジェクトの情報を出力します。

例 4. test_aggregation.php

<?php
include "storageclasses.inc";

// ユーティリティ関数

function p_arr($arr)
{
    foreach (
$arr as $k => $v)
        
$out[] = "\t$k => $v";
    return
implode("\n", $out);
}

function
object_info($obj)
{
    
$out[] = "クラス: " . get_class($obj);
    foreach (
get_object_vars($obj) as $var=>$val) {
        if (
is_array($val)) {
            
$out[] = "プロパティ: $var (array)\n" . p_arr($val);
        } else {
            
$out[] = "プロパティ: $var = $val";
        }
    }
    foreach (
get_class_methods($obj) as $method) {
        
$out[] = "メソッド: $method";
    }
    return
implode("\n", $out);
}


$data = array(M_PI, "kludge != cruft");

// 基本オブジェクトを作成する
$fs = new FileStorage($data);
$ws = new WDDXStorage($data);

// オブジェクトの情報を表示する
echo "\$fs オブジェクト\n";
echo
object_info($fs) . "\n";
echo
"\n\$ws オブジェクト\n";
echo
object_info($ws) . "\n";

// 集約を行う

echo "\n\$fs を WDDXStorage クラスに集約します\n";
aggregate($fs, "WDDXStorage");
echo
"\$fs オブジェクト\n";
echo
object_info($fs) . "\n";

echo
"\nそれを DBStorage クラスに集約します\n";
aggregate($fs, "DBStorage");
echo
"\$fs オブジェクト\n";
echo
object_info($fs) . "\n";

echo
"\nWDDXStorage を集約から解除します\n";
deaggregate($fs, "WDDXStorage");
echo
"\$fs オブジェクト\n";
echo
object_info($fs) . "\n";

?>

出力内容を見ながら、PHP の集約についての副作用や制限事項を 考えてみましょう。 まず、新しく作成されたオブジェクト $fs および $ws は、期待通りの (対応するクラス定義に もとづく) 結果を出力します。 PHP では実際のところクラス/オブジェクトの要素についてパブリック/ プライベートの区別はありませんが、集約の際には クラス/オブジェクトのプライベート要素はアンダースコア文字 ("_") で始まるとみなします。

$fs オブジェクト
クラス: filestorage
プロパティ: data (array)
    0 => 3.1415926535898
    1 => kludge != cruft
メソッド: filestorage
メソッド: write

$ws オブジェクト
クラス: wddxstorage
プロパティ: data (array)
    0 => 3.1415926535898
    1 => kludge != cruft
プロパティ: version = 1.0
プロパティ: _id = ID::9bb2b640764d4370eb04808af8b076a5
メソッド: wddxstorage
メソッド: store
メソッド: _genid

次に $fsWDDXStorage クラスと集約し、オブジェクトの情報を出力します。 $fs は今でも FileStorage のままですが、プロパティ $version およびメソッド store() が存在することがわかるでしょう。これらは いずれも WDDXStorage で定義されているものです。 注意すべき点は、クラスで定義されているプライベート要素は集約されていない ということです。それらは $ws オブジェクトの中には 存在します。また、WDDXStorage のコンストラクタも 存在しません。これを集約するのは論理的ではありません。

$fs を WDDXStorage クラスに集約します
$fs オブジェクト
クラス: filestorage
プロパティ: data (array)
    0 => 3.1415926535898
    1 => kludge != cruft
プロパティ: version = 1.0
メソッド: filestorage
メソッド: write
メソッド: store

集約処理は、積み重ねていくことが可能です。そこで今度は $fsDBStorage を 集約します。これにより、定義されているすべてのクラスの 保存処理メソッドを使用可能なオブジェクトができあがります。

それを DBStorage クラスに集約します
$fs オブジェクト
クラス: filestorage
プロパティ: data (array)
    0 => 3.1415926535898
    1 => kludge != cruft
プロパティ: version = 1.0
プロパティ: dbtype = mysql
メソッド: filestorage
メソッド: write
メソッド: store
メソッド: save

最後に、プロパティやメソッドを動的に集約したのと同じ方法で、 集約したプロパティやメソッドを解除することも可能です。 $fs から WDDXStorage クラスの集約を解除すると、このような結果になります。

WDDXStorage を集約から解除します
$fs オブジェクト
クラス: filestorage
プロパティ: data (array)
    0 => 3.1415926535898
    1 => kludge != cruft
プロパティ: dbtype = mysql
メソッド: filestorage
メソッド: write
メソッド: save

上で説明しきれなかったことがひとつあります。それは、集約処理の際には 既存のプロパティやメソッドは上書きされないということです。例えば、 FileStorage クラスでは $data というプロパティを定義しており、 WDDXStorage クラスでも同名のプロパティが 定義されていますが、このプロパティが FileStorage のインスタンス化の際に 取得したプロパティの内容を上書きすることはありません。

目次
aggregate_info --  オブジェクトに集約された各クラスのメソッドとプロパティを 連想配列で返す
aggregate_methods_by_list --  選択したクラスメソッドを、動的にオブジェクトに集約する
aggregate_methods_by_regexp --  正規表現を使用して選択したクラスメソッドを、 動的にオブジェクトに集約する
aggregate_methods --  クラスのメソッドを、動的にオブジェクトに集約する
aggregate_properties_by_list --  選択したクラスプロパティを、動的にオブジェクトに集約する
aggregate_properties_by_regexp --  正規表現を使用して選択したクラスプロパティを、 動的にオブジェクトに集約する
aggregate_properties --  クラスのプロパティを、動的にオブジェクトに集約する
aggregate --  メソッドおよびプロパティの動的なクラス/オブジェクト集約を行う
aggregation_info -- aggregate_info() のエイリアス
deaggregate --  集約されたメソッドやプロパティをオブジェクトから取り除く