Perl Moose类的依赖注入

Perl Moose类的依赖注入,perl,dependency-injection,moose,Perl,Dependency Injection,Moose,我有一个Moose类,它需要发送类型为Foo::Request的请求。我需要从外部访问此依赖关系,以便在测试中轻松交换请求实现。我提出了以下属性: has request_builder => ( is => 'rw', isa => 'CodeRef', default => sub { sub { Foo::Request->new(@_) } } ); 然后在代码中: my $self = shift; my

我有一个Moose类,它需要发送类型为
Foo::Request
的请求。我需要从外部访问此依赖关系,以便在测试中轻松交换请求实现。我提出了以下属性:

has request_builder => (
    is => 'rw',
    isa => 'CodeRef',
    default => sub {
        sub { Foo::Request->new(@_) }
    }
);
然后在代码中:

my $self = shift;
my $request = $self->request_builder->(path => …);
在测试中:

my $tested_class = …;
my $request = Test::MockObject->new;
$request->mock(…);
$tested_class->request_builder(sub { $request });

有更简单/更惯用的解决方案吗?

考虑以下方法:

在Moose类中定义一个名为
make\u request
的“抽象”方法。然后定义两个实现
make_request
的角色——一个调用
Foo::request->new
,另一个调用
Test::MockObject->new

例如:

您的主类和两个角色:

package MainMooseClass;
use Moose;
...
# Note: this class requires a role that
# provides an implementation of 'make_request'


package MakeRequestWithFoo;
use Moose::Role;
use Foo::Request; # or require it
sub make_request { Foo::Request->new(...) }

package MakeRequestWithMock;
use Moose::Role;
use Test::MockRequest;  # or require it
sub make_request { Test::MockRequest->new(...) }
如果要测试主类,请将其与“MakeRequestWithMock”角色混合使用:

package TestVersionOfMainMooseClass;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithMock';

package main;
my $test_object = TestVersionOfMainMooseClass->new(...);
如果您想将其用于“make_request”的Foo实现,请将其与“MakeRequestWithFoo”角色混合使用

一些优点:

您将只加载所需的模块。例如,类
TestVersionOfMainMooseClass
将不会加载模块
Foo::Request

您可以将实现
make_request
所需的相关数据添加为新类的实例成员。例如,您最初使用CODEREF的方法可以通过以下角色实现:

package MakeRequestWithCodeRef;
use Moose::Role;
has request_builder => (
  is => 'rw',
  isa => 'CodeRef',
  required => 1,
);
sub make_request { my $self = shift; $self->request_builder->(@_) };
要使用此类,您需要为
请求\u生成器
提供初始值设定项,例如:

package Example;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithCodeRef';

package main;
my $object = Example->new(request_builder => sub { ... });

最后,您编写的角色可能可用于其他类。

在使用Moose::Util::apply_all_角色的测试中动态应用角色如何?我想用这个已经有一段时间了,但还没有借口。以下是我认为它将如何工作

首先,稍微修改原始属性:

package MyClientThing;
has request => (
    is      => 'rw',
    isa     => 'Foo::Request',
    builder => '_build_request',
);
sub _build_request { Foo::Request->new };
....
然后创建一个Test::RequestBuilder角色:

package Test::RequestBuilder;
use Moose::Role;
use Test::Foo::Request; # this module could inherit from Foo::Request I guess?
sub _build_request { return Test::Foo::Request->new }; 
同时,在“t/我的客户”一文中,你可以这样写:

use MyClientThing;
use Moose::Util qw( apply_all_roles );
use Test::More;

my $client  = MyClientThing->new;
apply_all_roles( $client, 'Test::RequestBuilder' );  

isa_ok $client->request, 'Test::Foo::Request';

有关更多信息,请参见。

我的建议是,按照Coloric文章中的模型(上面由Mike评论),如下所示:

在你们班:

has request => (
    is => 'ro',
    isa => 'CodeRef',
    default => sub {
        Foo::Request->new(@_)
    }
);
在您的测试中:

my $request = Test::MockObject->new;
$request->mock(…);
my $tested_class = MyClass->new(request => $request, ...);
与代码完全相同,并进行以下改进:

  • 如果可能的话,将属性设置为只读,并在构造函数中设置它,以实现更好的封装
  • 您的
    请求
    属性是一个随时可用的对象;无需取消对子参考的引用

  • 我知道这篇文章有点老,但是对于现在提到这个问题的人来说,请求者可以使用这样的框架。

    这并不能回答你的问题,但是你可能会对Coloric的一篇短文感兴趣,它讨论了同样的技术:我喜欢这个解决方案,这就是我将如何解决它。coderef在如何构造对象方面给了你很大的灵活性。@Mike:谢谢你的链接。我以前读过这篇文章,在发布这个问题之前,我重新阅读了它。这是一本很好的读物,但并不是针对建筑商的。这只是针对“一次性”依赖项,而不是您的类需要动态构建的依赖项。实际上,如果您必须在类实现中使用生成器,那么您的示例就是正确的。谢谢。是的,没错,我需要一个构建器,所以我不能使用Coloric博客文章中介绍的简单对象设置器。看起来coderef构建器是最好的解决方案。