pluginやcomponentを取り扱うモジュールを書く時に、plugin/componentを取り扱いを全て引き受けてくれるモジュールを作りました。
だいぶ前からCPANには上げてたんですが、色々あって今日報告と。
既出のアイディアとしてはid:naoyaの Class::Pluggableやid:ya_kenの Class::Pluggable等があります。
これらのモジュールもシンプルでいい感じなのですが、シンプルな分、若干物足りなさがあった(例えばメソッドの生やし方に自由度持たせたいとか)のでこしらえました。
詳しくはCPANのClass::Componentにもありますが日本語でも説明文書きたい。
出来る事
- component追加で基本メソッド拡張が出来る
- plugin追加で、hook pointへのhook処理追加や、pluginメソッドを追加出来る
- pluginで追加するメソッドの実装方式(普通にメソッド生やす、AUTOLOAD、SingletonMethod)を選べる。選ばなければメソッド生えない
- Class::Componentを使ったオブジェクトをYAML::DumpとかしてもYAML::Loadすれば普通に動く
- Plaggerっぽくconfigを渡すとpluginまで設定が渡される
- pluginはattributeを使い、sub jitensya: Method {} のように書くだけでメソッド追加出来る
- Class::Component::Attribute::以下にモジュールを追加すると、任意のattribute処理が出来る
- 作ったモジュールを、さらに別のモジュールで継承する事も可能
- その場合のPluginの名前解決方法はClass::C3っぽい順番で解決する
- Class::C3の再発明
影響を受けたもの
Class::Component::Attribute::*に追加してpluginで使うattributeを拡張できるようにするのはCatalystから。
Class::Component::Pluginに書いてあるAttributeを集める仕組みはClass::DBIxから
configやhookなどはPlaggerから
Component
いわゆるCatalystのPlugin方式に拡張します。
単純に@ISAにパッケージを突っ込んで行って継承ツリーを太らせます。
ここには、モジュールの動作に多大な影響を与える物を置いておきます。
Attributeを拡張した時に、呼び出すメソッドなんかはComponentで。
Class::Componentには、標準でComponentが4つ付いて来て最低限の挙動をチョイス出来ます。
- DisableDynamicPlugin
- blessされたobjectからはpluginを追加出来なくします。__PACKAGE__->load_pluginsやMyClass->load_pluginsの形でしか追加出来なくなります。
後述しますがClass::Componentはblessされたオブジェクト別にpluginを管理出来るのですが、この機能をOFFにします。
若干new時のコストが押さえられます。 - Autocall::InjectMethod
- *{"$pkg::$method"} = sub {}な形で、pluginのメソッドをパッケージに割り当てます。
これやるとオブジェクト毎にメソッド管理出来なくなりますがシンプルです。 - Autocall::Autoload
- AUTOLOADを使ってオブジェクト毎に使えるメソッド名を判断して処理出来ます。
- Autocall::SingletonMethod
- その名の通りです。id:naoya氏の実装に近いです。
これはDisableDinamicPluginとの併用が出来ません。
どっちにしろ相反するcomponentではありますね。
Autocall::*の何れかを利用すれば$obj->plugin_methodの形で呼び出せますが
一切利用しないと$obj->call( plugin_method => options ... )の形でしか呼べなくなります。
AutoloadとSingletonMethodはg:id:naoya:Pluggableの話の時のSingletonMethodなのにAUTOLOADを使ってるコードをみて、諦めちゃいなよyou的なノリで二種類作った。
Plugin
PlaggerやSledgeとかClass::Triggerを使った時のhook処理の実装や、コアには影響ないけど用途によってチョイス出来るメソッドを拡張する為にあります。
書き方のサンプルはテストを見るのが一番です。
http://search.cpan.org/src/YAPPO/Class-Component-0.05/t/MyClass/Plugin/Hello.pm"
使い方も
http://search.cpan.org/src/YAPPO/Class-Component-0.05/t/02_myclass_autoload.t
で、componentの解説をした通り、標準ではblessされたオブジェクト毎にpluginの管理されていますので、同じClassから作ったオブジェクトでも呼べるメソッドと呼べないメソッドが発生します。
Dump/Load
YAML::DumpされたデータをYAML::Loadすると自動的にプラグインのロードをしてくれます。
ただし use Class::Component reload_plugin => 1; や use MyClass reload_plugin => 1;の用にuseしておく必要があります。
http://search.cpan.org/src/YAPPO/Class-Component-0.05/t/04_myclass_autoload_dump.tが動作例です。
利用しているComponentによっては旨く動かないかもしれません。
ComponentとPluginの読み込み方
package MyBase;
use base 'Class::Component';
__PACKAGE__->load_components(qw/ Autocall::Autoload /);
package MyClass;
use base 'MyBase';
__PACKAGE__->load_components(qw/ Foo +Bar::Component::Jitensya /);
__PACKAGE__->load_plugins(qw/ Boofy +Foo::Plugin::Baz /);
package main;
use MyClass;
MyClass->load_plugins(qw/ Hello /);
こんな感じの継承されたモジュールがあるとして
MyBaseクラスでのAutocall::Autoloadは、MyBase::Component::Autocall::Autoload, Class::Component::Autocall::Autoloadの順で存在してるか確認し、一番最初に見つかったパッケージを利用します。
MyClassでは
MyClass::Component::Foo MyBase::Component::Foo Class::Component::Component::Foo の順
次のは先頭に+が付いているとCatalyst/DBICと同じ挙動をするので、Bar::Component::Jitensyaを探します。
pluginも同じ感じ。
mainパッケージのも、MyClass,MyBase,Class::Componentの順で探します。
探す順番はClass::C3のような継承ツリーの順序で探索します。
NEXT
Class::C3と同じようなNEXTメソッドを再発明して実装してます。。。
色々不安な部分が合ったのでClass::C3を利用しませんでした。
Class::C3で問題が無いかの検証もしてない。。。
new
SUPERでもNETXでも何でもいいのでClass::Component::newまで処理投げて下さい><
おわり
パフォーマンスに気を使ったりだとか、use baseする側んに余計なメソッドを継承させないだとか考えていたら、だいぶコードがかっこわるくなって来た。
しかも、シンプルじゃなくなって来ている。。。
基礎クラスをプラガブルに作って、さらにその基礎クラスを継承して使うなんて用途もありかなと思ったり。
この話は、Number::Objectへもづく
Posted by Yappo at 2007年06月12日 21:34
| TrackBack
| Perl