Perl界隈では空前のMooseブームが起こっていて良いですね! そういえば,卒論で書いたコードでMooseを使っていたのを思い出したので,最近,使い方を把握したMoose::Roleを使ってリファクタリングしてみました.
とか思って,書いてみましたが,実際書いてみるとMoose::Roleの使い方としてはすごく微妙になってしまいました!概念として継承関係になっているのを強引にmix-inにするとかわけわからないです!なので,以下若干ぐだぐだ.
リファクタリング前のコード
リファクタリングの対象とするのは,抽象的なAST Parserを実装したクラスです.
対象言語に依存しないような抽象的なParserクラスとしてNyanco::AST::Parseを定義しています,
package Nyanco::AST::Parser; use Moose; use Nyanco::AST::Code; sub parse { my ($self, $source) = @_; my $content = $source->read(); my $root = $self->_create_tree($content); return Nyanco::AST::Code->new( root => $root, source => $source); } sub _create_tree { confess "You should override this method."; } no Moose;1;
このクラスでは,parseメソッドを呼ぶと,privateな_create_treeを使ってASTを作ります.Nyanco::AST::CodeがASTの実態クラスです.これは抽象クラスなので,_create_treeは実装されていなくて,例外を出すしかしていません.
これの実装のクラスとして,Nyanco::AST::Parser::Perlがあります.Perlのソースコードを読み込んでASTをつくります.
package Nyanco::AST::Parser::Perl; use Moose; use Nyanco::AST::Node::Token; use Nyanco::AST::Node::Block; use PPI; extends qw(Nyanco::AST::Parser); sub _create_tree { my ($self, $content) = @_; my $ppidoc = PPI::Document->new(\$content); my $root = $self->_walk($ppidoc); return $root; } sub _walk { # PPIでがんばってASTっぽいツリーをつくる! } no Moose; 1;
このクラスでは,Nyanco::AST::Parserを継承して_create_treeメソッドを実装しています.Nyanco::AST::Parserのparseメソッドは継承しているので,
Nyanco::AST::Parser::Perl->new->parse( $source )
というように,parseメソッドを使うことができます.
リファクタリング
さて,Nyanco::AST::ParserはMoose::Roleとして扱えそうです.Moose::Roleは複数のクラスの共通の機能を切り出して,ほかのクラスで利用できるようにする仕組みです.OOの継承よりもmix-inの概念に近そうですね.というわけで,前述のNyanco::AST::ParserをRoleに書き換えてみます.
package Nyanco::AST::Parser; use Moose::Role; use UNIVERSAL::require; use Nyanco::AST::Code; requires '_create_tree'; sub parse { my ($self, $source) = @_; my $content = $source->read(); my $root = $self->_create_tree($content); return Nyanco::AST::Code->new( root => $root, source => $source); } 1;
例外を送出するだけの,意味のない_create_treeメソッドを書く必要がなくなりました.かわりに requires '_create_tree' が増えてこのRoleを利用する場合_create_treeメソッドの実装が必要であることを示しています.
Nyanco::AST::Parser::Perlのほうというと,
package Nyanco::AST::Parser::Perl; use Moose; use Nyanco::AST::Node::Token; use Nyanco::AST::Node::Block; use PPI; with qw(Nyanco::AST::Parser); sub _create_tree { my ($self, $content) = @_; my $ppidoc = PPI::Document->new(\$content); my $root = $self->_walk($ppidoc); return $root; } sub _walk { # PPIでがんばってASTっぽいツリーをつくる! } no Moose; 1;
となります.extends qw(Nyanco::AST::Parser); が with qw(Nyanco::AST::Parser); になっただけですね.継承からRoleへの変更です.
リファクタリング完了
とりあえず,Moose::Roleを使って継承で実現していたことをやってみました.Moose::Roleを使った利点としては,
- 例外を出すだけのかっこわるい抽象メソッドが不要
- 抽象メソッドが明言されているのでわかりやすい
- 抽象クラスとして定義されるのでインスタンス化を防げる
とかが考えられそうです.
しかし,今回の例はあまり良くなくて,もともと継承関係で定義してすっきりしていた設計が,Roleをつかったmix-inっぽくなってかなり気持ち悪くなってしまいました.Moose::Cookbook::Recipe10にあるような,比較などのもうすこし一般的な概念をRoleとして取り出すほうがよさげです.
ただ,Javaっぽい抽象クラスやインターフェース機能はあるとうれしいので,Mooseの機能でRole以外の抽象クラスの実現方法があったらぜひ使いたいところです.だれか教えてー.