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

发布于 2024-09-13 04:02:43 字数 2192 浏览 6 评论 0原文

我正在使用 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');

...

I am using HTML::FormHandler. To use it one is supposed to subclass from it and then you can override some attributes such as field_name_space or attribute_name_space.

However, I now have lots of forms all extending HTML::FormHandler or its DBIC based variant HTML::FormHandler::Model::DBIC and therefore have these overidden attributes repeated many times.

I tried to put them in a role but get an error that +attr notation is not supported in Roles. Fair enough.

What is the best way of eliminating this repetition? I thought perhaps subclassing but then I would have to do it twice for HTML::FormHandler and HTML::FormHandler::Model::DBIC, plus I believe general thought was that subclassing is generally better achieved with Roles instead.

Update: I thought it would be a good idea to give an example. This is what I am currently doing - and it involves code repetition. As you can see one form uses a different parent class so I cannot create one parent class to put the attribute overrides in. I would have to create two - and that also adds redundancy.

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');

...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

野生奥特曼 2024-09-20 04:02:43

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

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

我的 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 应用程序中的表单数量通常相当高,并且需求可能非常多样化,因此我建议采用更灵活的解决方案。

First of all, roles are composed into a class, they have nothing to do with subclassing. A subclass is a full class that extends a parent (or more than one, but in my experience multiple inheritance should be avoided if it can be). A role is a piece of behaviour, or a parial interface that can be applied to a class. The role then directly modifies the class. There's no new class created in general.

So inheritance and role composition are really two different things and two different kinds of design. Thus you can't simply exchange one for the other. Both have different design-implications.

My strategy with HTML::FormHandler has been to make a real subclass for each form that I require, and put the different behaviours of the form that I wanted to re-use into roles.

I'd think this question (how to implement the extensions you need in a clean and sane way) can't really be answered without knowing the actual design you're aiming for.

Update: I see what you mean and that's a tricky case. HTML::FormHandler is primarily targetted at extension by inheritance. So I think the best strategy would indeed be to have two subclasses, one for HTML::FormHandler and one for HTML::FormHandler::Model::DBIC. It seems teadious at first, but you might want to have different settings for them in the long run anyway. To avoid repeating the actual configuration (the default values) I'd try the following (this example is plain HFH, without 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;

Note that you'd need to make an attribute lazy if it requires the values of another attribute for its computation. The above base class would look for hooks to find the initialized values. You'd have to do this in both classes, but you could put the default subroutine generation into a function you import from a library. Since the above doesn't require direct manipulation of the attribute anymore to change the default values, you can put that stuff in a role I called MyApp::Form::DefaultSettings above:

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

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

1;

This method will allow your roles to influence the default value construction. For example, you could have a role based on the one above that modifies the value with around.

There is also a very simple, but in my opinion kind-of ugly way: You could have a role provide a BUILD method that changes the values. This seems pretty straight-forward and easy at first, but it's trading extendability/flexibility with simplicity. It works simple, but also only works for very simple cases. Since the amount of forms in web applications is usually rather high, and the needs can be quite diverse, I'd recommend going with the more flexible solution.

怼怹恏 2024-09-20 04:02:43

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

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

The code for HTML::FormHandler::Model::DBIC is actually in a Moose trait in order to help with this situation. You can inherit from your base class, and in your forms that use the DBIC model, you can do

with 'HTML::FormHandler::TraitFor::Model::DBIC';
段念尘 2024-09-20 04:02:43

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

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

Would this method, using multiple inheritance (I know I know, ugh), where you put your common default overrides in one class, and then your customized code in others?

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

Now you can descend from MyApp::Form or MyApp::Form::Model::DBIC as needed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文