=encoding utf8 =pod =head1 題名 Moose::Cookbook::Meta::Recipe7 - グロブリファレンスをメタインスタンスのクラスにする =head1 概要 package My::Meta::Instance; use Scalar::Util qw( weaken ); use Symbol qw( gensym ); use Moose; extends 'Moose::Meta::Instance'; sub create_instance { my $self = shift; my $sym = gensym(); bless $sym, $self->_class_name; } sub clone_instance { my ( $self, $instance ) = @_; my $new_sym = gensym(); %{*$new_sym} = %{*$instance}; bless $new_sym, $self->_class_name; } sub get_slot_value { my ( $self, $instance, $slot_name ) = @_; return *$instance->{$slot_name}; } sub set_slot_value { my ( $self, $instance, $slot_name, $value ) = @_; *$instance->{$slot_name} = $value; } sub deinitialize_slot { my ( $self, $instance, $slot_name ) = @_; delete *$instance->{$slot_name};; } sub is_slot_initialized { my ( $self, $instance, $slot_name, $value ) = @_; exists *$instance->{$slot_name};; } sub weaken_slot_value { my ( $self, $instance, $slot_name ) = @_; weaken *$instance->{$slot_name};; } sub inline_create_instance { my ( $self, $class_variable ) = @_; return 'do { my $sym = Symbol::gensym(); bless $sym, ' . $class_variable . ' }'; } sub inline_slot_access { my ( $self, $instance, $slot_name ) = @_; return '*{' . $instance . '}->{' . $slot_name . '}'; } package MyApp::User; use metaclass 'Moose::Meta::Class' => ( instance_metaclass => 'My::Meta::Instance' ); use Moose; has 'name' => ( is => 'rw', isa => 'Str', ); has 'email' => ( is => 'rw', isa => 'Str', ); =head1 本文 このレシピでは自前のメタインスタンスの作り方を紹介します。メタインスタンスというのはオブジェクトのインスタンスを作るメタクラスのことで、アトリビュートスロットへのアクセスを管理する手助けをします。 ここで作るメタインスタンスは、ハッシュリファレンスではなく、グロブリファレンスベースのものです(この例の元ネタはおもにPiotr RoszatyckiのLモジュールです)。 私たちのクラスはLのサブクラスですが、これはハッシュリファレンスベースのオブジェクトを作るものなので、オブジェクトのデータ構造がそうなっていることを前提にしているメソッドはすべてオーバーライドする必要があります。 最初にオーバーライドするメソッドはCです。 sub create_instance { my $self = shift; my $sym = gensym(); bless $sym, $self->_class_name; } ここではメタインスタンスに紐づけられたクラスでblessされたグロブリファレンスを返します。 また、Cもオーバーライドして、新しいグロブリファレンスを生成するようにします。 sub clone_instance { my ( $self, $instance ) = @_; my $new_sym = gensym(); %{*$new_sym} = %{*$instance}; bless $new_sym, $self->_class_name; } それから、オブジェクトのスロットへのアクセスを仲介する一連のメソッドがあります(「スロット」というのはアトリビュートが保存される場所です)。デフォルトのインスタンスクラスでは、オブジェクトはハッシュリファレンスであることが期待されていますが、ここを、グロブリファレンスであることを期待するように変更する必要があります。 sub get_slot_value { my ( $self, $instance, $slot_name ) = @_; *$instance->{$slot_name};; } このレベルの回り道をすると、おそらくインスタンスクラスはデフォルトのものより「遅く」なってしまいますが、アトリビュートのアクセスをインライン展開してしまえば、この参照はキャッシュされます。 sub inline_create_instance { my ( $self, $class_variable ) = @_; return 'do { my $sym = Symbol::gensym(); bless $sym, ' . $class_variable . ' }'; } Cメソッドが返すコード片は、ひとつのアトリビュートにつき1度Cされます。 最後に、Cクラスの中でこのメタインスタンスを使うようにします。 use metaclass 'Moose::Meta::Class' => ( instance_metaclass => 'My::Meta::Instance' ); 実際にはほとんどの場合、Lの利用は推奨しません。ただし、もう一方の、メタクラスの代用品を利用するやり方はもっと複雑ですし、例題のコードも必要以上に込み入ったものになってしまいます。 =head1 まとめ このレシピでは独自のメタインスタンスクラスの作り方を紹介しました。みなさんがここまでする必要はなさそうですが、Mooseが裏でどのような動きをしているかを見てみるのも一興です。 =head1 参照 CPANにはメタインスタンスクラスの拡張モジュールがいくつかあります。 =over 4 =item * L このモジュールはインスタンスクラスを拡張してオブジェクトが確実にシングルトンになるようにします(ただし、このモジュールが利用するインスタンスはそれでもblessされたハッシュリファレンスのままです)。 =item * L このモジュールはインスタンスをblessされたグロブリファレンスにします。こうするとハンドルをオブジェクトインスタンスとして利用できるようになります。 =back =head1 作者 Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT AND LICENSE Copyright 2006-2009 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =pod