wfsp has asked for the wisdom of the Perl Monks concerning the following question:

I've recently been bitten by not being consistent. Before settling on one approach I'd appreciate the views of the monks.

For example, a new method calls a private init method. Should the init method return attributes?

sub new { my ($class, $args) = @_; my $self = {}; bless ($self, $class); my ($attr_1, $attr_2) = $self->my_init($args); $self->{attr_1} = $attr_1; $self->{attr_2} = $attr_2; return $self; } sub my_init { my ($self, $args) = @_; # do stuff with $args; my $attr_1 = 1; my $attr_2 = 2; return ($attr_1, $attr_2); }
or just set them:
sub new { my ($class, $args) = @_; my $self = {args => $args}; bless ($self, $class); $self->my_init; return $self; } sub my_init { my ($self) = @_; my $args = $self->{args} # do stuff with $args $self->{attr_1} = $attr_1; $self->{attr_2} = $attr_2; }
To my eye the latter looks neater but does the "action at a distance" make it less clear/maintainable than the former?

I'm using both approaches at the minute and I've met myself coming back. Are there better alternatives?

Replies are listed 'Best First'.
Re: Passing object attributes around
by GrandFather (Saint) on Apr 26, 2008 at 11:13 UTC

    It's a private init method and the code is right next to the new (probably): just set em - what else is an init for?

    Adding a pile of common code to the constructor so it performs half the initialization just seems silly, especially if there are multiple constructors for the object and the init provides the common work.

    You're not hiding stuff away (the "action at a distance" concern) because you've said up front that the init routine is going to init stuff - that's what an init routine is for after all. Any maintainer who can't figure out that a routine called init is going to update the object in interesting ways probably is in for a bunch of trouble (or is about to create a bunch of trouble) anyway.


    Perl is environmentally friendly - it saves trees
Re: Passing object attributes around
by mscharrer (Hermit) on Apr 26, 2008 at 11:38 UTC
    Set them.
    After the OO idea methods are messages to the object.
    So you tell the object with my_init() "please init yourself" - and that it should do.
    In other OO enabled languages like C++ it is done the same - ok, someone could argue that returning an array is not easy in this language.

    Update: I would call such a function _init(). The leading underscore indicates an internal function.

Re: Passing object attributes around
by stiller (Friar) on Apr 26, 2008 at 11:49 UTC
    In (general|ideal) conditions (how to make those merge??), I don't think object methods should pass attributes between them.

    Argument passing between methods should mainly be for carrying user suplied values to the place where the sanity of them is to be determined, and where the value might be approved.

Re: Passing object attributes around
by dragonchild (Archbishop) on Apr 26, 2008 at 16:56 UTC
    Use Moose. That way, you don't care.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Passing object attributes around
by doom (Deacon) on Apr 27, 2008 at 00:28 UTC
    I seem to be on the other side of the fence on this one from everyone else. I think your first way seems a little cleaner, at the very least it's the way I tend to do things: inside my "init" I might call a "generate_something" method that returns "something" which I then use to set the "something" attribute.

    What everyone else is telling you is essentially that the wall of the object class is the only encapsulation barrier that you need to respect, and when you're on the inside of it anything goes (or almost anything). What I would suggest is that the situation is a little fuzzier than that, and it's not a bad idea to try to organize the way communication happens inside of the object, as well as between objects.

    Possibly a related issue: I often find that when I want to make sure that one thing is kept up to date if a change happens to something else, it's very tempting to bury a call inside the "something_else" accessor to some other routine that will refresh what ever needs to be kept up to date. That's a very robust (if not always efficient) way of doing things -- the trouble is when I come back six months later, I tend to forget about it, and assume that all my accessors are simple and stupid accessors. So: nice trick, but it's good to take it easy on it...

      To call a method to calculate one attribute is totally ok and something else than a method which calculates all attributes and returns them as a list.
Re: Passing object attributes around
by bart (Canon) on Apr 27, 2008 at 06:44 UTC
    I don't think the former has less "action at a distance", on the contrary — though theorists may disagree with my terminology. In the former, you calculate the attributes, but you don't set them in init; you actually set the attributes in new. There, you depend on the specific order the attribute values are returned. That is bad: you split a logical action (setting attributes) between two tightly bound subs. Add or remove one attribute, and you have to modify both subs, and very strictly in sync. That is what makes it bad for maintenance. Surely it's better to do it all just in one place, in one single sub?

    Besides, re "action at a distance", that applies to setting global variables, but that's not the case here, as you alter an object you pass as a read/write parameter.

    If you insist on turning init into a function, at least, make it return a hash.

    sub my_init { my ($self, $args) = @_; # do stuff with $args; return { attr_1 => $attr_1, attr_2 => $attr_2 }; } sub new { my ($class, $args) = @_; my $self = {}; bless ($self, $class); my $attrs = $self->my_init($args); foreach (keys %$attrs) { $self->{$_} = $attrs->{$_}; } return $self; }
    At least it breaks the internal dependency on which attributes, and in what order, are returned.

    I still prefer directly setting the attributes for the object you passed as a parameter. It smells cleaner to me: there's far less distracting "janitor code" in it. (That is code that is necessary for an implementation but actually doesn't do much useful for its perceived complexity; where nothing specifically applies to this particular piece of code. Up to 4 lines of code for a simple "copy attributes" action: ugh.)

Re: Passing object attributes around
by shmem (Chancellor) on Apr 27, 2008 at 11:56 UTC

    For proper advice, some questions need answers. You label my_init() as a private method. Why? Is that method called from some other method (public or private) of the package? In your code examples my_init() is unconditionally called from new(). Why?

    As you post them, your new() and my_init() methods could as well be merged into one. Make up your mind on why you need the attribute population separated from the constructor.

    There are of course good reasons for doing that.

    • an empty object might be needed before the attributes are even known
    • there might be a need to re-initialize the attributes of an existing object

    For either use, my preferred idiom is

    sub new { my $class = shift; my $self = bless {}, $class; @_ ? $self->init(@_) : $self; } sub init { my $self = shift; # do parameter validation, set attributes ... # and return the object $self; }

    IMHO attributes should only be passed around using setters/getters, and an init() subroutine is a catch-all setter method to me. It might do other initializing stuff, though.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}