【问题标题】:What is the most efficient way to override an attribute in lots of my Moose based sub classes?在许多基于 Moose 的子类中覆盖属性的最有效方法是什么?
【发布时间】:2010-08-03 12:41:39
【问题描述】:

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

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

我试图将它们放入一个角色中,但得到一个错误,即角色中不支持+attr 表示法。很公平。

消除这种重复的最佳方法是什么?我想可能是子类化,但后来我不得不为HTML::FormHandlerHTML::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');

...

【问题讨论】:

    标签: perl moose


    【解决方案1】:

    首先,角色被组合成一个类,它们与子类化无关。子类是一个完整的类,它扩展了一个父类(或多个,但根据我的经验,如果可以的话,应该避免多重继承)。角色是一种行为,或可应用于类的部分接口。然后角色直接修改类。一般不会创建新的类。

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

    我对@9​​87654321@ 的策略是为我需要的每个表单创建一个真正的子类,并将我想要重用的表单的不同行为放入角色中。

    我认为如果不了解您的实际设计目标,就无法真正回答这个问题(如何以干净和理智的方式实现所需的扩展)。

    更新:我明白你的意思,这是一个棘手的案例。 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 应用程序中的表单数量通常相当多,而且需求可能非常多样化,因此我建议使用更灵活的解决方案。

    【讨论】:

    • 是的,我明白这一点。我的意思是我已经设法通过使用角色来减少对子类化的依赖,它对我很有帮助。不过,这对我没有帮助。我添加了一个示例,以便您查看我的问题。
    【解决方案2】:

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

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

    【讨论】:

    • 这会不会降低效率?
    • 没有。如果您查看 HTML::FormHandler::Model::DBIC 包,它还包含使用 'with' 的特征。所以'with'只是在不同的地方完成。唯一的问题是,如果当前包中包含 trait,如果你想调用模型的 update_model 方法(通常使用 next::method 完成),'update_model' 方法可能必须使用方法修饰符。跨度>
    • 太好了 - 我认为这一定是我发布此问题后添加的一项功能。非常感谢您在此模块上所做的所有辛勤工作,Gerda!太棒了。
    【解决方案3】:

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

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

    【讨论】:

    • 我会考虑的。不确定我是否可以安全地继承 HTML::FormHandler 和 HTML::Formhandler::Model::DBIC
    猜你喜欢
    • 2023-03-31
    • 2013-06-23
    • 2011-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-01-08
    • 1970-01-01
    • 2023-03-20
    • 2013-01-07
    相关资源
    最近更新 更多