Moose "builder" против "default"

Я понимаю, что использование builder позволяет подклассам легко переопределять атрибуты по умолчанию, а роли могут require их. Это также можно выполнить с помощью default следующим образом:

has 'foo' =>
    is       => 'rw',
    isa      => 'Str',
    default  => sub { $_[0]->_build_foo };

Мне интересно, есть ли дополнительные преимущества для использования builder, о которых я не знаю? Я сам придумал:

  • builder является декларативным, поэтому вы можете понять, что foo создается _build_foo
  • builder исключает оболочку подпрограммы, что делает ее немного быстрее
  • builder позволяет использовать полезный lazy_build.

UPDATE. Чтобы прояснить это, речь идет не о default vs builder вообще, а default => sub { $_[0]->_build_foo } vs builder => '_build_foo'.

Ответы

Ответ 1

Думаю, вы уже ответили на свой вопрос. Использование builder допускает позднюю привязку, которая прекрасно сочетается с ролями и классами, которые предназначены для подкласса. Это также полезно, если строитель довольно длинный - я никогда не ставил default больше, чем строку в определение атрибута. Там нет реальной функциональной разницы; default может легко эмулировать builder, но результат не очень красивый.

Ответ 2

Используя "построитель" и "по умолчанию", вы можете сделать код более удобным для чтения и организации.

'builder' также может соответствовать знакомому шаблону программирования, где частные методы начинаются с подчеркивания.

has json => ( is => 'ro', default => sub { JSON->new } )
has schema => ( is => 'ro', builder => '_schema' }

sub _schema {
  my $self = shift;
  $self->log_debug('constructing schema') if($self->debug);
  My::App::Schema->connect($self->dsn,$self->username,$self->password)  
}

Кроме того, использование построителя позволяет превращать дорогостоящие функции в memoized accessors, не касаясь оригинального метода:

sub get_things {
  my $self = shift;
  return +{ map { $_ => $self->price_for($_) }
    $self->wodgets->calulate_expensive_things };

Рефакторинг с memoization:

has things => ( is => 'ro', lazy => 1, builder => 'get_things' );

Это большинство способов, которыми я использовал строитель для уточнения кода.

Ответ 3

Нет никакой разницы между

default => sub { $_[0]->_build_foo }

и

builder => '_build_foo'

Основное различие между default и builder заключается в том, что один вызывает anon sub, а другой вызывает именованный метод.

has created_time_stamp => (
   default => sub { time() },
);

против

has created_time_stamp => (
   builder => '_build_created_time_stamp',
);

sub _build_created_time_stamp { time() }

Использование default уменьшает прокрутку кода, поскольку все, где вам это нужно. Я использую его по этой причине. То есть использование менее типизации является бонусом.

Это также заставляет вас быть более явным в переопределении строителя. Другие ответы рассматривали этот конфликт, но я считаю, что вызов виртуальных методов на объекте, который еще не был сконструирован, является плохой практикой! Для чего BUILD.