为什么在使用 Moose 时 "very bad practice" 覆盖 new?
Why is it "very bad practice" to override new when using Moose?
来自 Moose::Manual::BestPractices 页面:
Overriding new
is a very bad practice. Instead, you should use a BUILD
or BUILDARGS
methods to do the same thing. When you override new
, Moose can no longer inline a constructor when your class is immutabilized.
我的问题是为什么这被认为是非常糟糕的做法?
我认为 "inline" 构造函数只是意味着构造函数与 class 在同一个包中定义。如果这是真的,是否意味着如果您在该包中覆盖 new
构造函数仍将被视为 "inline" 构造函数?如果我错了,请纠正我。我不完全理解构造函数 "inline."
意味着什么的概念
我遇到这个问题的原因是因为我正在创建一个构建对象列表的脚本。如果用户尝试创建一个与列表中的对象相同的新对象,我想停止 Moose
创建新对象,而只是 return 对现有对象的引用。
创建新对象时,我想将其推送到列表中,然后 return 新对象。当尝试创建现有对象时,现有对象应该被 returned 而不是推送到列表中。
# Some pseudo code showing the logic of what I attempted
around BUILDARGS => sub {
my ($orig, $self, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object
# I want this to directly return the object
# and skip the call to BUILD
}
return $self->orig(
# Insert args here
);
};
sub BUILD {
my ($self) = @_;
# I don't want this call to happen if the object already existed
push @list, $self;
}
创建新对象时,我尝试使用 BUILD
在创建后将其推送到列表中。问题是当我试图创建一个现有对象并使用 BUILDARGS
到 return 时,现有对象似乎并没有阻止 Moose
调用 BUILD
试图推动对象添加到列表中。
我能够解决这个问题的唯一方法是覆盖 new
并使其 return 现有对象而不创建新对象。
# Some pseudo code showing the overridden constructor
sub new {
my ($class, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object
}
# Build the object
my $self = bless {
# Insert args here
}, $class;
# Add the object to the list
push @list, $object;
}
覆盖 new
有效,但如果它真的是一个可怕的想法,正如 Moose
文档似乎暗示的那样,是否有更好的方法来做到这一点?
内联子例程意味着在调用点复制其代码,而不是插入子例程调用。这要快得多,因为它避免了在堆栈上收集参数和任何局部变量以及调用和 return 操作的开销。如果 class 没有被声明为不可变的,这是不可能的,因为任何突变都可能意味着构造函数必须改变,因此不能再插入内联
除了 Moose
无法 内嵌 构造函数之外,我还发现了覆盖 new
的另一个问题。我发现如果你重写 new
,它会阻止 Moose
在调用构造函数时创建属性 "required"。
例如,如果您覆盖 new
并使用如下属性定义 class:
has 'var' => (is => 'rw', isa => 'Str', => required => 1);
Moose
将允许您创建此 class 的新实例,而无需为其传递 var
.
的值
我找到了我想做的可行的解决方案,至少我认为它是可行的。我真的不需要 内联 构造函数的速度(我没有创建数百万个对象),但我确实需要 "require" 属性的能力。如果您使用 Moose 提供的 around
函数,您基本上可以覆盖 new,而不会阻止 Moose
能够 "require" 属性。
例如:
around new => sub {
my ($orig, $class, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object without calling new
}
# Create a new object
my $self = $class->orig(%args);
# Add it to the list of objects.
push @list, $self;
};
这种方法仍然会警告您无法创建 内联 构造函数,但至少它会正常运行。
注意:将 __PACKAGE__->meta->make_immutable('inline_constructor' => 0);
添加到包的末尾将抑制此警告。
来自 Moose::Manual::BestPractices 页面:
Overriding
new
is a very bad practice. Instead, you should use aBUILD
orBUILDARGS
methods to do the same thing. When you overridenew
, Moose can no longer inline a constructor when your class is immutabilized.
我的问题是为什么这被认为是非常糟糕的做法?
我认为 "inline" 构造函数只是意味着构造函数与 class 在同一个包中定义。如果这是真的,是否意味着如果您在该包中覆盖 new
构造函数仍将被视为 "inline" 构造函数?如果我错了,请纠正我。我不完全理解构造函数 "inline."
我遇到这个问题的原因是因为我正在创建一个构建对象列表的脚本。如果用户尝试创建一个与列表中的对象相同的新对象,我想停止 Moose
创建新对象,而只是 return 对现有对象的引用。
创建新对象时,我想将其推送到列表中,然后 return 新对象。当尝试创建现有对象时,现有对象应该被 returned 而不是推送到列表中。
# Some pseudo code showing the logic of what I attempted
around BUILDARGS => sub {
my ($orig, $self, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object
# I want this to directly return the object
# and skip the call to BUILD
}
return $self->orig(
# Insert args here
);
};
sub BUILD {
my ($self) = @_;
# I don't want this call to happen if the object already existed
push @list, $self;
}
创建新对象时,我尝试使用 BUILD
在创建后将其推送到列表中。问题是当我试图创建一个现有对象并使用 BUILDARGS
到 return 时,现有对象似乎并没有阻止 Moose
调用 BUILD
试图推动对象添加到列表中。
我能够解决这个问题的唯一方法是覆盖 new
并使其 return 现有对象而不创建新对象。
# Some pseudo code showing the overridden constructor
sub new {
my ($class, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object
}
# Build the object
my $self = bless {
# Insert args here
}, $class;
# Add the object to the list
push @list, $object;
}
覆盖 new
有效,但如果它真的是一个可怕的想法,正如 Moose
文档似乎暗示的那样,是否有更好的方法来做到这一点?
内联子例程意味着在调用点复制其代码,而不是插入子例程调用。这要快得多,因为它避免了在堆栈上收集参数和任何局部变量以及调用和 return 操作的开销。如果 class 没有被声明为不可变的,这是不可能的,因为任何突变都可能意味着构造函数必须改变,因此不能再插入内联
除了 Moose
无法 内嵌 构造函数之外,我还发现了覆盖 new
的另一个问题。我发现如果你重写 new
,它会阻止 Moose
在调用构造函数时创建属性 "required"。
例如,如果您覆盖 new
并使用如下属性定义 class:
has 'var' => (is => 'rw', isa => 'Str', => required => 1);
Moose
将允许您创建此 class 的新实例,而无需为其传递 var
.
我找到了我想做的可行的解决方案,至少我认为它是可行的。我真的不需要 内联 构造函数的速度(我没有创建数百万个对象),但我确实需要 "require" 属性的能力。如果您使用 Moose 提供的 around
函数,您基本上可以覆盖 new,而不会阻止 Moose
能够 "require" 属性。
例如:
around new => sub {
my ($orig, $class, %args) = @_;
# loop through objects in list
for my $object (@list) {
# if $args used to define the new object
# match the arguments of any object in the list
# return the matched object without calling new
}
# Create a new object
my $self = $class->orig(%args);
# Add it to the list of objects.
push @list, $self;
};
这种方法仍然会警告您无法创建 内联 构造函数,但至少它会正常运行。
注意:将 __PACKAGE__->meta->make_immutable('inline_constructor' => 0);
添加到包的末尾将抑制此警告。