Каков наиболее эффективный способ переопределить атрибут во многих моих подклассах, основанных на Moose? - PullRequest
2 голосов
/ 03 августа 2010

Я использую HTML :: FormHandler . Чтобы использовать его, нужно создать из него подкласс, а затем вы можете переопределить некоторые атрибуты, такие как field_name_space или attribute_name_space.

Однако теперь у меня есть множество форм, расширяющих HTML::FormHandler или его вариант на основе DBIC HTML :: FormHandler :: Model :: DBIC , и, следовательно, эти повторные атрибуты повторяются много раз.

Я пытался поместить их в роль, но получаю сообщение об ошибке, что запись +attr не поддерживается в ролях. Достаточно справедливо.

Каков наилучший способ устранения этого повторения? Я подумал, что, возможно, создание подклассов, но тогда мне придется сделать это дважды для HTML::FormHandler и HTML::FormHandler::Model::DBIC, плюс я считаю, что общая мысль заключалась в том, что подклассы обычно лучше достигаются с помощью ролей.

Обновление: Я подумал, что было бы неплохо привести пример. Это то, что я сейчас делаю - и это включает повторение кода. Как вы можете видеть, одна форма использует другой родительский класс, поэтому я не могу создать один родительский класс для помещения переопределений атрибута. Мне нужно создать два - и это также добавляет избыточность.

package MyApp::Form::Foo;

# this form does not interface with DBIC
extends 'HTML::Formhandler';

has '+html_prefix'          => (default => 1); 
has '+field_traits'         => (default => sub { ['MyApp::Form::Trait::Field'] }); 
has '+field_name_space'     => (default => 'MyApp::Form::Field');
has '+widget_name_space'    => (default => sub { ['MyApp::Form::Widget'] }); 
has '+widget_wrapper'       => (default => 'None');

...

package MyApp::Form::Bar;

# this form uses a DBIC object
extends 'HTML::Formhandler::Model::DBIC';

has '+html_prefix'          => (default => 1); 
has '+field_traits'         => (default => sub { ['MyApp::Form::Trait::Field'] }); 
has '+field_name_space'     => (default => 'MyApp::Form::Field');
has '+widget_name_space'    => (default => sub { ['MyApp::Form::Widget'] }); 
has '+widget_wrapper'       => (default => 'None');

...

package MyApp::Form::Baz;

# this form also uses a DBIC object
extends 'HTML::Formhandler::Model::DBIC';

has '+html_prefix'          => (default => 1); 
has '+field_traits'         => (default => sub { ['MyApp::Form::Trait::Field'] }); 
has '+field_name_space'     => (default => 'MyApp::Form::Field');
has '+widget_name_space'    => (default => sub { ['MyApp::Form::Widget'] }); 
has '+widget_wrapper'       => (default => 'None');

...

Ответы [ 3 ]

4 голосов
/ 03 августа 2010

Прежде всего, роли составлены в класс, они не имеют ничего общего с подклассами. Подкласс - это полный класс, который расширяет родительский класс (или более одного, но по моему опыту следует избегать множественного наследования, если это возможно). Роль - это часть поведения или частичный интерфейс, который можно применить к классу. Затем роль напрямую изменяет класс. В общем, новый класс не создан.

Итак, наследование и ролевая композиция - это действительно две разные вещи и два разных вида дизайна. Таким образом, вы не можете просто обменять одно на другое. Оба имеют разные дизайнерские последствия.

Моя стратегия с HTML::FormHandler состояла в том, чтобы создать реальный подкласс для каждой формы, в которой я нуждаюсь, и поместить различные формы поведения, которые я хотел повторно использовать, в роли.

Я думаю, что на этот вопрос (как реализовать расширения, которые вам нужны, чистым и разумным способом) невозможно ответить без знания фактического дизайна, к которому вы стремитесь.

Обновление: Я понимаю, что вы имеете в виду, и это сложный случай. HTML::FormHandler в первую очередь предназначен для расширения наследованием. Поэтому я думаю, что лучшей стратегией действительно было бы иметь два подкласса, один для HTML::FormHandler и один для HTML::FormHandler::Model::DBIC. Поначалу это кажется утомительным, но в любом случае вы, возможно, захотите иметь разные настройки для них. Чтобы не повторять фактическую конфигурацию (значения по умолчанию), я бы попробовал следующее (этот пример - простой HFH, без DBIC):

package MyApp::Form;
use Moose;
use namespace::autoclean;

extends 'HTML::FormHandler';
with 'MyApp::Form::DefaultSettings';

# only using two fields as example
for my $field (qw( html_prefix field_traits )) {
    has "+$field", default => sub {
        my $self   = shift;
        my $init   = "_get_default_$field";
        my $method = $self->can($init)
          or die sprintf q{Class %s does not implement %s method}, ref($self), $init;
        return $self->$method;
    };
}

1;

Обратите внимание, что вам нужно сделать атрибут ленивым, если для его вычисления требуются значения другого атрибута. Вышеупомянутый базовый класс будет искать хуки для поиска инициализированных значений. Вы должны сделать это в обоих классах, но вы можете поместить подпрограмму по умолчанию в функцию, которую вы импортируете из библиотеки. Поскольку вышеописанное больше не требует прямого манипулирования атрибутом для изменения значений по умолчанию, вы можете поместить этот материал в роль, которую я назвал MyApp::Form::DefaultSettings выше:

package MyApp::Form::DefaultSettings;
use Moose::Role;
use namespace::autoclean;

sub _build_html_prefix  { 1 }
sub _build_field_traits { ['MyApp::Form::Trait::Field'] }

1;

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

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

1 голос
/ 05 февраля 2013

Код для HTML::FormHandler::Model::DBIC на самом деле является чертой Moose, чтобы помочь в этой ситуации.Вы можете наследовать от своего базового класса, и в ваших формах, которые используют модель DBIC, вы можете сделать

with 'HTML::FormHandler::TraitFor::Model::DBIC';
0 голосов
/ 03 августа 2010

Будет ли этот метод, используя множественное наследование (я знаю, я знаю, тьфу), где вы помещаете свои обычные переопределения по умолчанию в один класс, а затем свой настраиваемый код в другие?

package MyApp::Form;

use Moose;
extends 'HTML::Formhandler';

has '+html_prefix'          => (default => 1); 
has '+field_traits'         => (default => sub { ['MyApp::Form::Trait::Field'] }); 
has '+field_name_space'     => (default => 'MyApp::Form::Field');
has '+widget_name_space'    => (default => sub { ['MyApp::Form::Widget'] }); 
has '+widget_wrapper'       => (default => 'None');

package MyApp::Form::Model::DBIC;

use Moose;
extends 'MyApp::Form', 'HTML::Formhandler::Model::DBIC';

# ... your DBIC-specific code

Теперь вы можетепри необходимости перейдите из MyApp :: Form или MyApp :: Form :: Model :: DBIC.

...