在我的许多基于Moose的子类中覆盖属性的最有效方法是什么?

时间:2010-08-03 12:41:40

标签: perl moose

我正在使用HTML::FormHandler。要使用它,我们应该从中继承它,然后您可以覆盖某些属性,例如field_name_spaceattribute_name_space

但是,我现在有很多表单都扩展HTML::FormHandler或其基于DBIC的变体HTML::FormHandler::Model::DBIC,因此重复多次这些重叠属性。

我尝试将它们置于某个角色中,但却收到错误,即角色不支持+attr表示法。很公平。

消除这种重复的最佳方法是什么?我想也许是继承,但是我必须为HTML::FormHandlerHTML::FormHandler::Model::DBIC做两次,而且我相信一般的想法是使用Roles通常可以更好地实现子类化。

更新:我认为举个例子是个好主意。这就是我目前正在做的 - 它涉及代码重复。正如您所看到的,一个表单使用不同的父类,因此我无法创建一个父类来放置属性覆盖。我必须创建两个 - 这也会增加冗余。

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 个答案:

答案 0 :(得分:4)

首先,角色组成一个类,它们与子类化无关。子类是一个完整的类,它扩展了父类(或多个,但根据我的经验,如果可以的话,应该避免多重继承)。角色是一种行为,或者可以应用于类的parial接口。然后角色直接修改类。一般来说,没有新的课程。

因此,继承和角色组合实际上是两种不同的东西和两种不同的设计。因此,你不能简单地为另一个交换一个。两者都有不同的设计含义。

我使用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方法。这一开始看起来非常简单直接,但它具有简单易用性和灵活性。它工作简单,但也只适用于非常简单的情况。由于Web应用程序中的表单数量通常很高,而且需求可能非常多样化,因此我建议使用更灵活的解决方案。

答案 1 :(得分:1)

HTML::FormHandler::Model::DBIC的代码实际上是Moose特征,以帮助解决这种情况。您可以从基类继承,并且在使用DBIC模型的表单中,您可以执行

with 'HTML::FormHandler::TraitFor::Model::DBIC';

答案 2 :(得分:0)

这个方法,使用多重继承(我知道,我知道,呃),在一个类中放置常见的默认覆盖,然后在其他类中放置自定义代码?

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下载。