取势 明道 优术

作者为 扶 凯 发表

概述

  package Request;
  use Moose;
  use Moose::Util::TypeConstraints;

  use HTTP::Headers  ();
  use Params::Coerce ();
  use URI            ();

  subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');

  coerce 'My::Types::HTTP::Headers'
      => from 'ArrayRef'
          => via { HTTP::Headers->new( @{$_} ) }
      => from 'HashRef'
          => via { HTTP::Headers->new( %{$_} ) };

  subtype 'My::Types::URI' => as class_type('URI');

  coerce 'My::Types::URI'
      => from 'Object'
          => via { $_->isa('URI')
                   ? $_
                   : Params::Coerce::coerce( 'URI', $_ ); }
      => from 'Str'
          => via { URI->new( $_, 'http' ) };

  subtype 'Protocol'
      => as 'Str'
      => where { /^HTTP\/[0-9]\.[0-9]$/ };

  has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
  has 'uri'  => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
  has 'method'   => ( is => 'rw', isa => 'Str' );
  has 'protocol' => ( is => 'rw', isa => 'Protocol' );
  has 'headers'  => (
      is      => 'rw',
      isa     => 'My::Types::HTTP::Headers',
      coerce  => 1,
      default => sub { HTTP::Headers->new }
  );

描述

这个指南展示了类型的强制转换(强制多态|强制转换 coercion),这只是定义的一个实现多态的功能糖.它是在现有前面讲的类型约束上来附加强制转换的功能和定义了一个类型转换到另一个类型的方法.
这个功能非常强大的,但它可能产生一些意想不到的结果.所以你需要明确的告诉这个属性是被强制的才使用.要定义这个需要开启 coerce 属性选项为真.

首先,我们使用前面的子类型的功能来创建一个其它类型, 到时我们将要在这个上面强制转换成另一种类型.

subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');

首先我们要创建一个子类型,而不直接使用 HTTP::Headers 本身的类型.我们这样做的原因是,这个强制转换是全局的.当在目前的 Perl 解释器的定义所有 Moose 使用类定义时,会进行这种强制转换,转换会用在我们 Request 的类中的 HTTP::Headers 上. 这是一个最佳实践,以避免这种命名空间污染.

 

这 class_type 的功能糖仅仅是强制转换的快捷方式.

subtype 'HTTP::Headers'
      => as 'Object'
      => where { $_->isa('HTTP::Headers') };

 

Moose 的在内部, Moose 会为每个 Moose 的类创建一个类型约束,但非 Moose 的类,类型必须显式声明才能使用.

我们可以继续向前学习,直接使用这个新的类型:

has 'headers' => (
      is      => 'rw',
      isa     => 'My::Types::HTTP::Headers',
      default => sub { HTTP::Headers->new }
  );

这将创建一个简单的属性,默认为空 HTTP::Headers 的实例方法.
这个 HTTP::Headers 的构造器,它接受一个键值对的 HTTP header 的列表,这个在 Perl 中,这个列表会存成 ARRAY or HASH 的引用.我们要采取适当的一些操作来保证正常的接受这样的数据结构.不然 HTTP::Headers 的实例方法会出错.这操作正是强制转换(coerce)可以做的操作:

 coerce 'My::Types::HTTP::Headers'
      => from 'ArrayRef'
          => via { HTTP::Headers->new( @{$_} ) }
      => from 'HashRef'
          => via { HTTP::Headers->new( %{$_} ) };

 

coerce 的第一个参数是我们需要强制转换的类型.他会设置 “从什么(from)/通过什么(via)” 的条款和条件, 从什么(from)的功能拿需要转换的其它的类型的类型名字,通过什么(via)的功能是一个子程序的引用,它来进行实际的转换.
不过,默认定义的这个强制转换功能是不做任何事,直到告诉 Moose 我们想对这个指定的属性进行转换操作:

has 'headers' => (
      is      => 'rw',
      isa     => 'My::Types::HTTP::Headers',
      coerce  => 1,
      default => sub { HTTP::Headers->new }
  );

 

现在,如果我使用 ArrayRef 或者 HashRef 这几种类型来填充 headers,它会转换成新的 HTTP::Headers 的实例.其实你要实现转换这种类型一样,下面的代码可以得到相同的结果(因为这是 Perl 哈哈).

$foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) );
$foo->headers( [ 'bar', 1, 'baz', 2 ] );
$foo->headers( { bar => 1, baz => 2 } );

就象你所见到的,使用强制转换能有非常开放的类接口,同时这个仍保留“sefety安全”的类型约束检查. (1)

 

我们接下来告诉你学习我们如何能够利用现有的 CPAN 模块,以帮助进行转换.在这个例子中,我们使用 Params::Coerce.
再次,我们需要在非 Moose 的 URI 的类中先声明类类型.

subtype 'My::Types::URI' => as class_type('URI');

我们定义这个强制转换功能

coerce 'My::Types::URI'
      => from 'Object'
          => via { $_->isa('URI')
                   ? $_
                   : Params::Coerce::coerce( 'URI', $_ ); }
      => from 'Str'
          => via { URI->new( $_, 'http' ) };

 

第一个转换是对任何对象都转换成一个 URI 对象,因为转换系统并不是智能的,他不能检查出对象中是否有一个 URI .因此,这里是我自己检查,如果它不存在 URI 的对象,我们就使用 Params::Coerce , 我们只是使用这个模块它的返回值.
如果 Params::Coerce 不返回 URI 的对象(无论出于何种原因),Moose 就会抛出一个类型约束错误.

另一种类型的转换是将拿个字符串转换成一个 URI 对象.在这种情况下,我们通过应用强制转换的默认行为,给符串转为一个 HTTP 的 URI 对象.

最后,你必须记住,在属性选项中打开强制转换的功能为真.

has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );

重用强制转换,你可以强制使用多个属性一致的API.

 

 

结论

这个指南演示了使用强制转换来创建更灵活的 API. 然而,任何强大的功能,都是希望我们谨慎一些.有时候,我们猜测它的值不如直接拒绝它.
这是告诉你怎么使用 class_type 的功能糖,他只是一个快捷方式连接到一个对象定义的新的子类型.

 

备注

(1)只有这个特殊的例子可以更安全.我们真的要转换类型到一个偶数个元素的数组.我们可以创建一个新的 EvenElementArrayRef 类型,然后做类型强制,而不是使用一个普通的 ArrayRef 的类型.

 

 

 

来了就留个评论吧! 1个评论