Moose > Moose::Cookbook::Roles::Recipe1

題名

Moose::Cookbook::Roles::Recipe1 - Moose::Roleの例

概要

  package Eq;
  use Moose::Role;

  requires 'equal_to';

  sub not_equal_to {
      my ( $self, $other ) = @_;
      not $self->equal_to($other);
  }

  package Comparable;
  use Moose::Role;

  with 'Eq';

  requires 'compare';

  sub equal_to {
      my ( $self, $other ) = @_;
      $self->compare($other) == 0;
  }

  sub greater_than {
      my ( $self, $other ) = @_;
      $self->compare($other) == 1;
  }

  sub less_than {
      my ( $self, $other ) = @_;
      $self->compare($other) == -1;
  }

  sub greater_than_or_equal_to {
      my ( $self, $other ) = @_;
      $self->greater_than($other) || $self->equal_to($other);
  }

  sub less_than_or_equal_to {
      my ( $self, $other ) = @_;
      $self->less_than($other) || $self->equal_to($other);
  }

  package Printable;
  use Moose::Role;

  requires 'to_string';

  package US::Currency;
  use Moose;

  with 'Comparable', 'Printable';

  has 'amount' => ( is => 'rw', isa => 'Num', default => 0 );

  sub compare {
      my ( $self, $other ) = @_;
      $self->amount <=> $other->amount;
  }

  sub to_string {
      my $self = shift;
      sprintf '$%0.2f USD' => $self->amount;
  }

本文

Rolesにはおもに2つの目的があります。インタフェースと、コードの再利用です。このレシピでは、比較を定義するロールとオブジェクトのコードを表示するロールを利用して、コードの再利用について説明します。

まずはEqから見ていきましょう。最初にuse Mooseuse Moose::Roleになっているところに注目してください。また、requiresという新しいシュガー関数も使われています。

  requires 'equal_to';

これは、このロールを取り込むクラスはかならずequal_toというメソッドを用意する必要がある、という意味です。このメソッドは、ロールを取り込むクラスに直書きしてもよいですし、ほかのロールから取り込んでもかまいません。

Eqロールには、要求したequal_toメソッドを利用したnot_equal_toというメソッドが定義されています。このようにすると、ロールを取り込むクラスが用意しなければならないメソッドを最小限にとどめることができます。

次のComparableロールは、Eqロールを下敷きにして作られています。ここではwithという新しいシュガー関数を利用してEqComparableの中に取り込んでいます。

  with 'Eq';

with関数は取り込みたいロールのリストを引数に取ります。この例では、Eqが要求しているequal_toメソッドをComparableロールが用意していますが、そのようにしないこともできます(その場合はComparableを取り込むクラスが自前でequal_toを用意する必要があります)。言い換えると、ロールは必須メソッドを用意「しなくても」ほかのロールを取り込める、ということです。

Comparableロールはcompareというメソッドを要求します。

  requires 'compare';

また、Comparableロールはほかにも最終的にはcompareに依存するいくつかのメソッドを提供しています。

  sub equal_to {
      my ( $self, $other ) = @_;
      $self->compare($other) == 0;
  }

  sub greater_than {
      my ( $self, $other ) = @_;
      $self->compare($other) == 1;
  }

  sub less_than {
      my ( $self, $other ) = @_;
      $self->compare($other) == -1;
  }

  sub greater_than_or_equal_to {
      my ( $self, $other ) = @_;
      $self->greater_than($other) || $self->equal_to($other);
  }

  sub less_than_or_equal_to {
      my ( $self, $other ) = @_;
      $self->less_than($other) || $self->equal_to($other);
  }

最後はPrintableロールです。このロールはインタフェースを提供するためだけに存在しています(メソッドはなく、必須メソッドのリストしかありません)。この場合、to_stringメソッドだけを要求しています。

インタフェースロールが便利なのは、メソッドと「名前」の双方を定義してくれるからです。このロールを組み込んだクラスはかならずto_stringメソッドを持っていることがわかるだけでなく、このメソッドが期待通りの意味を持っていることも想定できます(おそらく実際のコードではPrintableロールのドキュメントにメソッドの意味が書かれるはずです)。

最後のUS::Currencyクラスでは、ComparableロールとPrintableロールを両方取り込んでいます。

  with 'Comparable', 'Printable';

また、amountという、通常のMooseのアトリビュートも定義されています。

  has 'amount' => ( is => 'rw', isa => 'Num', default => 0 );

最後に、私たちのロールが要求しているメソッドの実装を見ていきます。compareメソッドはこうです。

  sub compare {
      my ( $self, $other ) = @_;
      $self->amount <=> $other->amount;
  }

Comparableロールを取り込んでこのメソッドを定義してやると、equal_togreater_thanless_thangreater_than_or_equal_toless_than_or_equal_toというメソッドが自由に使えるようになります。

あとは、to_stringメソッドです。

  sub to_string {
      my $self = shift;
      sprintf '$%0.2f USD' => $self->amount;
  }

まとめ

ロールは非常に強力なものになりえます。再利用可能な振る舞いをカプセル化したり、クラスが提供するメソッドの(意味やインタフェースといった)情報をやりとりしたりするうえではすばらしいツールです。

1

RunnerProcessという2つのクラスがあるものと思ってください。どちらにもrunというメソッドがありますが、オブジェクトにrunメソッドの実装を要求するだけでは、このメソッドが「実際になにをするか」はまだなんとも言えません。ところが、Executableロールを実装しているオブジェクトを要求していれば、ある程度意味がわかるようになります。

作者

Stevan Little <stevan@iinteractive.com>

Dave Rolsky <autarch@urth.org>

コピーライト & ライセンス

Copyright 2006-2009 by Infinity Interactive, Inc.

http://www.iinteractive.com

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.