ObjectのnewとメソッドのDispatch

モジュールがコマンド行から起動された時に、 そのモジュールの定義するクラスのオブジェクトを new し、 更に第一引数に基づいてメソッドの呼び出しを行う、という方法です。

(私はこのパターンを、 OO Modulino と呼んでいます)

以下の例ではサブコマンド名 $cmd に対して cmd_$cmd$cmd の二種類の メソッド呼び出しの規約を用意しています。こうすることにより、 標準入出力や終了コードを操作したいケースも対応できます。

unless (caller) {
  my $obj = __PACKAGE__->new(__PACKAGE__->parse_options(\@ARGV));

  my $cmd = shift || 'help';

  if (my $sub = $obj->can("cmd_$cmd")) {
    # cmd_FOO があるので、後は任せる
    $sub->($obj, @ARGV);

  } elsif ($sub = $obj->can($cmd)) {
    # 一般の任意のメソッドを試すケース
    my @res = $sub->($obj, @ARGV);

    # 呼び出し側で、デフォルトのシリアライザを通して出力
    print Data::Dumper->new([@res])->Terse(1)->Indent(0)->Dump, "\n";

  } else {
    $obj->cmd_help("No such command: $cmd");

  }
}

この方法のメリットは

  • $self とそこから初期化される変数に限れば)オブジェクトを扱える

  • 前節同様、メソッドの引数に HASH/ARRAY を渡したい場合は JSON を使う方法が使える

    • parse_options でも JSON の decode を行えば、HASH/ARRAY を new に渡せる
  • 出力も JSON に揃えると、組み合わせやすさが更に向上する

  • Modulino 化のためのモジュールを作っておくことも可能。 CLI から Object の API へのマッピングが統一できるので、使えば使うほど馴染む。 私の MOP4Import::Base::CLI_JSON も参考にどうぞ

  • サブコマンドと new のオプションを補完できると、更に使用感が改善する

この方法の短所

  • 既存のモジュールの、引数に Object を期待するメソッドは、正しく呼び出せない

つまり、最初から SCALAR か生の HASH/ARRAY しか受け取らないように 設計されたメソッドしか、コマンド行からは試せない事を意味します。

しかし逆にこの短所は、特に何でも Web API との親和性が求められがちな 現代のソフトウェア環境では、下手に class の付いた Object を 引数・戻り値として受け渡しするより、生 HASH/ARRAY をやり取りしたほうが 安全確実であるという考え方もありそうです。 ですので、これはひどい弱点にはならないかもしれない…私はそう考えています。

また、生HASH を扱う際の構造の保証には、fields 宣言による静的検査を使うことも 出来るでしょう。