From the Moose::Manual::BestPractices page:
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.
My question is why is this considered very bad practice?
I thought an "inline" constructor just meant that the constructor was defined in the same package as the class. If this is true wouldn't it mean that if you overrode new
within that package the constructor would still be considered an "inline" constructor? Please correct me if I'm wrong. I don't fully understand the concept of what it means for a constructor to be "inline."
The reason I came across this question is because I'm creating a script that builds a list of objects. If a user attempts to create a new object that is identical to one in the list I want to stop Moose
from creating a new object and just return a reference to the existing one.
When a new object is created I want to push it onto the list and return the new object. When an attempt is made to create existing object the existing object should be returned and not pushed onto the list.
# 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;
}
When creating a new object I attempted to use BUILD
to push it onto the list once it is created. The problem is when I attempted to create an existing object and use BUILDARGS
to return the existing object doesn't seem to stop Moose
from calling BUILD
which attempts to push the object onto the list.
The only way I was able to get around this was to override new
and have it return the existing object without creating a new one.
# 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;
}
Overriding new
worked, but if it really is such a horrible idea, as the Moose
documentation seems to suggest, is there a better way to do it?
I found another issue with overriding
new
, other thanMoose
not being able to in-line the constructor. I discovered that if you overridenew
, it preventsMoose
from being able to make attributes "required" when calling the constructor.For example if you override
new
and define a class with an attribute like so:has 'var' => (is => 'rw', isa => 'Str', => required => 1);
Moose
will allow you to create a new instance of this class without passing it a value forvar
.I found a workable solution for what I want to do, at least I think it's workable. I don't really need the speed of an in-line constructor (I'm not creating millions of objects), but I do need the ability to "require" attributes. If you use the
around
function provided by Moose you can essentially override new, without preventingMoose
from being able to "require" attributes.ex:
This approach still gives you the warning about not being able to create an in-line constructor, but at least it will function correctly.
Note: adding
__PACKAGE__->meta->make_immutable('inline_constructor' => 0);
to the end of your package will suppress this warning.