Class::Stdを使用したインサイドアウトクラスの作り方の基本
Perlベストプラクティスを参照してClass::Stdを利用したインサイドアウトクラスを使うようにしてみたので、基本のところを整理しておきます。
インサイドアウトクラス、Class::Stdを使用する目的などですが..
インサイドアウトクラスを使うことによりインスタンス属性へのカプセル化をする。通常のブレスされたハッシュを使う方法だと、ハッシュでのアクセスと同様の方法で属性にアクセスできてしまうので、属性をレキシカルなハッシュで管理する。
インサイドアウトクラスを使うと属性ごとのハッシュでのインスタンスデータの管理が行われる。インスタンス破棄時にそのハッシュデータのクリーンが必要ななるが、個々にたっているとメンドーなことになりので、Class::Stdを使って、このインスタンスのクリーンを自動化する。
継承を行うようになると(特に多重継承)、イニシャライズ、クリーンアップの順番などがややこしくなるのでClass::Stdを使用して、各階層個別のイニシャライズ、クリーンアップが適切に呼び出されるよう自動化。
実装例
とりあえず、コード例としては、以下のような2つの属性をもつ簡単なものを。
use strict; use warnings; package Cart::Item; use Class::Std; { my %item_id :ATTR; my %quantity :ATTR; sub BUILD { my ($self, $ident, $args_ref) = @_; $item_id{$ident} = $args_ref->{item_id} ? $args_ref->{item_id} : ''; $quantity{$ident} = $args_ref->{quantity} ? $args_ref->{quantity} : ''; } sub DEMOLISH { my ($self) = @_; print($self->item_id . " demolished \n"); } sub item_id { my $self = shift; $item_id{ident $self} = shift if @_; return $item_id{ident $self}; } sub quantity { my $self = shift; $quantity{ident $self} = shift if @_; return $quantity{ident $self}; } sub to_string() { my $self = shift; "item_id=" . $self->item_id . ",quantity=" . $self->quantity } }
実装に関するメモなど
「use Class::Std」として、Class::Stdをimportする。これにより、new、DESTROYなどのインスタンス構築、破棄に関するメソッドなどのここで定義したパッケージへのエクスポートがされる。
クラスの定義本体はカプセル化されるよう、レキシカルスコープ内に入れる。
インスタンスの属性は、Blessされたハッシュを使うのではなく、個々にハッシュとしてして宣言。インスタンスのIDをキー、インスタンス値をするHashでのインスタンス属性値管理を行うことになる。そして、このハッシュには:ATTR属性マーカーをつける。この:ATTRをつけることにより、ここで宣言した属性ハッシュを一括して管理できるようになり、インスタンス破棄時に属性ハッシュからの破棄が自動的に行われるようになる。( 属性マーカ宣言されたときに起動される「MODIFY_HASH_ATTRIBUTE」ハンドラー - Class::Stdで定義 - によって)。
インスタンスのイニシャライザはBUILDという名称で定義する。Class::StdからエクスポートされるnewメソッドがこのBUILDが呼び出される仕組みとなっていて、継承を行ったときも適切な順番でこのBUILDイニシャライザが呼び出されるようになっている。また、引数はインスタンス、インスタンスのID,newに渡された引数(Hashへの参照として渡される)となる。
インスタンスの破棄は、DEMOLISHという名称で定義する。Class::Stdからデストラクタ(DESTROY)がエクスポートされる仕組みになっていて、このデストラクタからDEMOLISHが呼び出されるようになっている。インスタンス属性ハッシュからの破棄は自動的に行われるので、単なる破棄でない(ファイルのクローズなど)もののクリーンアップの処理をこのDEMOLISHで行うことになる。これらのクリーンアップの処理は継承を行ったときも適切な順番で行われるようになっている。
インスタンス属性値へのアクセスは、属性ハッシュにからインスタンスIDをキーにして行うことになる。インスタンスIDは、Class::Stdで定義されているidentメソッド(=Scalar::Util::refaddrメソッドへの参照)で行うことができる。
以下、確認例。コンストラクタへの引数は、ハッシュへの参照となる、
my $item1 = Cart::Item->new({item_id=>'A-1', quantity=>2}); print($item1->to_string . "\n"); $item1->quantity(4); my $item2 = Cart::Item->new({item_id=>'A-2', quantity=>3}); print($item1->to_string . "\n"); print($item2->to_string . "\n");
インスタンス値の参照、更新がうまくいっていること、クリーンアップメソッド(DEMOLISH)が実行されていることが確認できる。