Perlでプラガブルモジュールを作ろう!

第1回 Class::Componentから始めるプラガブルモジュール

この記事を読むのに必要な時間:およそ 3 分

最小構成での実装例

Gopperは少々繁雑な処理をしているため,各要素を利用した最小限のサンプルモジュールを書きます。

MyClassモジュールを作成し,MyClassモジュールを利用するexample.plというスクリプトを書いたと想定します。

example.pl

use strict;
use warnings;
use MyClass;
my $obj = MyClass->new({
    config => {
        Hello => {
            msg => 'world'
        }
    }
});
$obj->hello; # hello world を表示
print $obj->run_hook( 'bar' )->[0]; # bog を表示
print $obj->baz; # news を表示

newしたときのオプションとしてconfigを渡しています。

configはPluginの名前をkeyにしてvalueに,そのPluginに対する設定を指定します。

MyClass

package MyClass;
use strict;
use warnings;
use Class::Component;
__PACKAGE__->load_components(qw/ Autocall::Autoload /);
__PACKAGE__->load_plugins(qw/ Hello Baz /);
1;

load_componentsにより,Class::Component::Component::Autocall::Autoloadをロードしています。

このComponentは,Pluginで拡張されるメソッドをPerlのAUTOLOADを使って,メソッドを生やすComponentになっています。

Autocall::*と名前がついているComponentは,動的にメソッドを生やす物になっていて,複数種類ある理由は,それぞれメソッドを生やす実装が異っていて,利用する方が最適な実装をチョイス出来るようにしているためです。

これが,コアの挙動を変更できるというComponentの役割を最もあらわしています。

また,load_pluginsによってMyClass::Plugin::HelloとMyClass::Plugin::Bazプラグインがロードされています。

MyClass::Attribute::News

package MyClass::Attribute::News;
use strict;
use warnings;
use base 'Class::Component::Attribute';

sub register {
    my($class, $plugin, $c, $method, $value, $code) = @_;
    no strict 'refs';
    no warnings 'redefine';
    my $cname = ref($plugin) or return;
    *{"$cname\::$method"} = sub {
        $code->(@_);
        'news';
    };
}
1;

NewsというAttributeを定義します。

このAttributeを定義されたメソッドは,どのような戻り値を指定したとしてもnewsという文字列を戻すようになります。

MyClass::Plugin::Hello

package MyClass::Plugin::Hello;
use strict;
use warnings;
use base 'Class::Component::Plugin';

sub hello :Method {
    my($self, $c, $args) = @_;
    print 'hello ' . $self->config->{msg};
}

sub hello_hello :Hook('bar') {
    my($self, $c, $args) = @_;
    'bog'
}
1;

helloメソッドではexample.plスクリプトの中で $obj->hello として呼び出される先となっていて,hello_helloメソッドはexample.plスクリプトの中で $obj->run_hook( 'bar' ) として呼び出されている先となっています。

それぞれ,Class::Componentにデフォルトで実装されているClass::Component::Attribute::MethodとClass::Component::Attribute::Hookを利用しています。

helloメソッドはexample.plで設定されたconfigを用いて戻り値を作成しています。

MyClass::Plugin::Baz

package MyClass::Plugin::Baz;
use strict;
use warnings;
use base 'Class::Component::Plugin';

sub baz :Method :News {
    my($self, $c, $args) = @_;
    'hello baz method'
}
1;

bazメソッドの戻り値はhello baz methodになるかと思いきや,MyClass::Attribute::Newsを利用しているため,どのような値を返したとしてもnewsになります。

Plugin1つにつき,Methodなどが1つしか書けないという訳ではなく,いくつでも書くことが出来ます。

名前空間の補間

load_componentsやload_pluginsに渡す引数は,自動的に名前空間を補間されて利用されます。

例えば今回のMyClassを例に取ると,HelloはMyClass::Plugin::Helloと補間してくれます。

もし,MyClass::Plugin::Helloが存在しなかった場合には@ISAの継承順に探索し,Class::Component::Plugin::Helloが存在すれば,それを利用します。

ComponentやAttributeに関しても同様にMyClass::(Component|Attribute)::*が無ければClass::Component::(Component|Attribute)::*を探します。

厳密に言うと@ISAの継承順ではなくClass::C3の探索ルールと殆ど同じアルゴリズムで探索を行ないます。

次回予告

今回は,Class::Componentのチュートリアルとして基本的な利用方法の紹介をしました。

次回以降から,Class::Componentを実装する上でのヒントとなった既存CPANモジュールの実装を交えながら,より実践的にClass::Componentを利用したプラガブルモジュール作成の方法を紹介していきます。

著者プロフィール

大沢和宏(おおさわかずひろ)

開発3センター サービス開発3室。翻訳Botなど,初期のBotサービス関連の開発を担当,その後LINE Taiwanのサービスを経て,現在はLINE LIVEのサーバ開発を行っている。

URL:https://github.com/yappo/

コメント

コメントの記入