驼鹿属性:分离数据和行为

Lum*_*umi 2 perl moose

我有一个用Moose构建的类,它本质上是一个文章列表的数据容器.所有属性-样name,number,price,quantity-用于数据."好吧,还有什么?",我能听到你说.还有什么?

不幸的情况的邪恶阴谋现在迫使外部功能进入该包:该类中的数据的税收计算必须由外部组件执行.这个外部组件紧密耦合到整个应用程序,包括破坏组件可测试性的数据库和依赖项,将其拖入一切耦合在一起的炖菜.(即使考虑从炖菜中重构税收成分也是完全不可能的.)

所以我的想法是让类接受包装税计算组件的coderef.然后,该类将保持独立于税收计算实现(及其可能的依赖性噩梦),同时它将允许与应用程序环境集成.

有'tax_calculator',is =>'ro',isa =>'CodeRef';

但是,我已经为我的班级添加了一个非数据组件.为什么这是一个问题?因为我(ab)$self->meta->get_attribute_list用于为我的类组装数据导出:

my %data; # need a plain hash, no objects
my @attrs = $self->meta->get_attribute_list;
$data{ $_ } = $self->$_ for @attrs;
return %data;
Run Code Online (Sandbox Code Playgroud)

现在coderef是属性列表的一部分.当然,我可以过滤掉它.但我不确定我在这里做的任何事情都是一种合理的方式.那么你如何处理这个问题,被认为需要分离数据属性和行为属性?

Cha*_*ens 7

可能有一半经过深思熟虑的解决方案:使用继承.像今天一样创建你的类,但使用在调用时死亡的calculate_tax方法(即虚函数).然后创建子类,覆盖该方法以调用外部系统.您可以测试基类并使用子类.

备用解决方案:使用角色添加calculate_tax方法.您可以创建两个角色:Calculate :: Simple :: Tax和Calculate :: Real :: Tax.在测试时添加简单角色,在生产中添加真实角色.

我掀起了这个例子,但我没有使用Moose,所以我可能会对如何将角色应用到课堂上感到疯狂.Moosey可能会采用更多方式:

#!/usr/bin/perl

use warnings;

{
    package Simple::Tax;
    use Moose::Role;

    requires 'price';

    sub calculate_tax {
        my $self = shift;
        return int($self->price * 0.05);
    }
}


{
    package A;
    use Moose;
    use Moose::Util qw( apply_all_roles );

    has price => ( is => "rw", isa => 'Int' ); #price in pennies

    sub new_with_simple_tax {
        my $class = shift;
        my $obj = $class->new(@_);
        apply_all_roles( $obj, "Simple::Tax" );
    }
}

my $o = A->new_with_simple_tax(price => 100);
print $o->calculate_tax, " cents\n";
Run Code Online (Sandbox Code Playgroud)

似乎在Moose中使用它的正确方法是使用两个角色.第一个应用于类并包含生产代码.第二个应用于您要在测试中使用的对象.它使用around方法颠覆了第一个方法,并且从不调用原始方法:

#!/usr/bin/perl

use warnings;

{
    package Complex::Tax;
    use Moose::Role;

    requires 'price';

    sub calculate_tax {
        my $self = shift;
        print "complex was called\n";
        #pretend this is more complex
        return int($self->price * 0.15);
    }
}

{
    package Simple::Tax;
    use Moose::Role;

    requires 'price';

    around calculate_tax => sub {
        my ($orig_method, $self) = @_;
        return int($self->price * 0.05);
    }
}


{
    package A;
    use Moose;

    has price => ( is => "rw", isa => 'Int' ); #price in pennies

    with "Complex::Tax";
}

my $prod = A->new(price => 100);
print $prod->calculate_tax, " cents\n";

use Moose::Util qw/ apply_all_roles /;
my $test = A->new(price => 100);
apply_all_roles($test, 'Simple::Tax');
print $test->calculate_tax, " cents\n";
Run Code Online (Sandbox Code Playgroud)