Как справиться с насмешливыми ролями в Moose? - PullRequest
4 голосов
/ 25 января 2012

Скажи, что у меня две роли: простая :: налоговая и реальная :: налоговая. В тестовых ситуациях я хочу использовать Simple :: Tax, а в производстве я хочу использовать Real :: Tax. Каков наилучший способ сделать это? Моей первой мыслью было использование разных версий метода new для создания объектов с разными ролями:

#!/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";

Моя вторая мысль заключалась в том, чтобы использовать оператор if в теле пакета, чтобы использовать различные операторы with:

#!/usr/bin/perl

use warnings;

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

    requires 'price';

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

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

    requires 'price';

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


{
    package A;
    use Moose;

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

    if ($ENV{TEST_A}) {
        with "Simple::Tax";
    } else {
        with "Complex::Tax";
    }
}

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

Является ли один из них лучше других, есть ли в них что-то ужасное, и есть ли лучший способ, о котором я еще не подумал.

1 Ответ

5 голосов
/ 27 января 2012

Моим первым предложением было бы что-то вроде MooseX::Traits, а затем указывались различные роли при создании объекта:

my $test = A->with_traits('Simple::Tax')->new(...);

my $prod = A->with_traits('Complex::Tax')->new(...);

Но это открывает дверь к созданию A без либо Роль применяется.Если подумать об этом дальше, я думаю, что у вас есть проблема X / Y.Если Simple::Tax используется только для макета Complex::Tax в тестовой среде, вы можете сделать несколько вещей, чтобы переопределить реализацию Complex :: Tax.

Например, вы можете просто определить Simple :: Tax следующим образом:

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

requires 'calculate_tax';
around calculate_tax => sub { int($_[1]->price * 0.05) };

Тогда всегда A составляет Complex::Tax и применяет Simple :: Tax к нему только во время тестов (используя apply_all_roles).

Если, тем не менее, вам нужны Simple :: Tax и Complex :: Tax как в производстве (так и не просто для тестирования), то вам лучше всего делать рефакторинг из отношения композиции (делает) к отношениям делегирования (имеет).

 package TaxCalculator::API;
 use Moose::Role;

 requires qw(calculate_tax);

 package SimpleTax::Calculator;
 use Moose;
 with qw(TaxCalculator::API);

 sub calculate_tax { ... }

 package ComplexTax::Calculator;
 use Moose;
 with qw(TaxCalculator::API);

 sub calcuate_tax { ... }


 package A;
 use Moose;

 has tax_calculator => ( 
      does => 'TaxCalculator::API', 
      handles => 'TaxCalculator::API', 
      default => sub { ComplexTax::Calculator->new() },
 );

Затем, если вы хотите переопределить его, вы просто передаете новый tax_calculator:

my $test = A->new(tax_calculator => SimpleTax::Calculator->new());

my $prod = A->new(tax_calculator => ComplexTax::Calculator->new());

Поскольку handles будет делегировать все методы из роли в качестве новых проксиэто практически то же самое, что составить роль самостоятельно.

...