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

I am trying to figure out the best way for an object to replace itself using a method, such that I can write while($object->load_next) rather than foreach(@objects). The only solution I have figued out is for the method to de-reference its own object and overwrite the object's hash, but this seems wrong. Details:

I have a persistence superclass I use to store my objects. One of its methods is ->list, which compiles a list of all stored instances of a particular class (i.e. every record in a database table). The method ->list will return an array if you wantarray, but otherwise it returns an object of the appropriate class with a method called ->load_next.

Calling ->load_next once loads then first stored instance of the class into the object. Calling it subsequently overwrites the object with the next stored class instance, until all stored class instances are exhausted, at which point the method returns undef.

I believe this is a fairly common pattern for object oriented design. In fact, it is modeled on the methods .List and .GetNext used by an experienced VB programmer.

But I'm not sure whether I know how to implement this properly in Perl. The method ->load_next can't simply overwrite the object self-reference it is passed, since such a change would only affect the method's own *copy* of the reference. Instead, I de-reference the object and overwrite its internal hash with the hash of the next object in the queue, sort of like an object copying method.

(Unlike a typical self-copy method, I don't have to re-bless the object, since existing referneces to the hash are already blessed.)

Is this the only way to pull this off? My method will not work for blessed arrays or scalars, and as I mentioned the method ->load_next is part of a superclass I want to use widely.

Example of the method in use, followed by actual code for ->load_next:

The method in use:

my $notes = My::Dashboard::Note->list; while ($notes->load_next){ print $notes->headline, $notes->created, $notes->body; }

The load_next method:

sub load_next{ my ($self, %arguments) = @_; my @next = @{$self->storage_next_list}; my $new_self = shift @next or return; %{$self} = %{$new_self}; $self->storage_next_list(list=>\@next); return $self; } sub storage_next_list{ my ($self, %arguments) = @_; $self->{storage}{next} = $arguments{list} if exists $arguments{list} +; return $self->{storage}{next}; }

Replies are listed 'Best First'.
Re: An object replacing itself
by dragonchild (Archbishop) on Sep 23, 2004 at 20:19 UTC
    First off, your list() method should return an iterator, which you would use as while (my $object = $iterator->next) { ... }. Each item in a list shouldn't know who the next item is - that's for the iterator to know.

    But, if you insist on doing this, you want to do the following:

    sub load_next{ my ($self, %arguments) = @_; my @next = @{$self->storage_next_list}; my $new_self = shift @next or return; $_[0] = $new_self; $self->storage_next_list(list=>\@next); return $new_self; }

    This is very poor coding and should be avoided.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      I do appreciate your comments.

      You may notice that I linked to someone else I respect using the setup I describe -- or at least doing so as near as I can tell. I would be grateful to know why you consider this "very poor coding" and advocate a different way of doing things. I have read Conway's book on OOP, the Camel several times over, never have I read, as far as I can recall, why this is a bad thing, so I'd appreciate knowing why I should join you in throwing rocks at this practice.

        This isn't a Perl question - this is a general programming question, so you wouldn't have seen it in any Perl-specific book, except in one of those sidebars. That's why places like here exist.

        The great Joel Spolsky

        • is wrong in the general context
        • is talking about something else

        The solution he is advocating ... there is an issue with how DAOs (Data Access Objects) are created in Java, JSP, ASP, and .Net applications. The interface is poor, to say the least. Joel is discussing a fix for a specific issue that causes many Windows developers great pain. This is similar to some of the solutions in ANSI C for memory management.

        The reason that the solution is poor in the general context has to do with what's known as "Separation of Concerns", more commonly known as "Who cares?". Items in a group should have no knowledge of the fact that they're in a group. The group should know how about the items in it and how to iterate over them. Often, a helper object is created, called an iterator, which can be exposed to the client that allows for syntactic sugar in iteration. (It also allows for more than one iterator to exist at one time.) The point is that the items in the group don't care about the other items in the group. The only entity that cares about that is the group itself. So, it should be the only one that can do anything about group membership.

        Put another way - finding the next member is closely linked to adding a new member. Would you ask an item in a group to add another member to said group? I wouldn't.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

        You have better ways to express this sort of thing - a collection should hand these objects out. An object that knows what is "next" already has an implicit collection somewhere otherwise it wouldn't know where it was and what came after it. dragonchild is right and you shouldn't be doing this.

        While I agree with dragonchild and diotalevi that you shouldn't do this, I'm not quite sure it's a terrible idea, per se. The only reason I would advise not to use it is, in Perl, there's a very common idiom for iterators:

        my $notelist = My::Dashboard::Note->list; while (my $note = $notelist->next) {}

        What you're trying to do may be common in VB-land, and there may be nothing inherently wrong with it, but you're not writing VB code. You're writing Perl code, and you should try to write things in a Perlish manner.

Re: An object replacing itself
by diotalevi (Canon) on Sep 23, 2004 at 20:32 UTC

    This is what for(;;) and container objects are for.

    for ( my $notes = My::Dashboard::Note->list, my $note = $notes->first; $note; $note = $notes->next ) { print $note->headline, $note->created, $note->body; }
Re: An object replacing itself
by JediWizard (Deacon) on Sep 23, 2004 at 20:19 UTC

    If I understand correctly, you want the object in the client code to become a different object when a particular method is called on it, correct? If this is want you want you can simply assign the new object to $_[0] in the method. See below:

    #File Foo.pm package Foo; sub change_var { $_[0] = 'Bar'; } # File main.pl use Foo; my $var = 'Foo'; print "$var\n"; # prints "Foo" Foo::change_var($var); print "$var\n"; # prints "Bar"

    In Perl the @_ array actually contains the arguments passed to the subroutine. Modifing @_ directly modifies the variable in the clients namespace. Does that make sense?

    May the Force be with you

      I'll try that, I may have made a bad assumption about something I tried before ...

Re: An object replacing itself
by dws (Chancellor) on Sep 24, 2004 at 06:08 UTC

    I believe this is a fairly common pattern for object oriented design.

    My experience suggests the opposite. Having an object that you're holding a reference to automagically morph into a different object when a method gets called somewhere else is an anti-pattern. It can lead to situations that are wickedly hard to debug, particularly when you pick up the code six months later, and have forgotten just how clever you were and where.

Re: An object replacing itself
by steves (Curate) on Sep 24, 2004 at 12:52 UTC

    I have found a similar construct useful in the context of a related class hierarchy, where an object turns itself into an object further down the inheritence chain. An example is this: You have a base file class. The constructor of that class takes a file name and provides back an object representing that file. Now suppose you have a subclass for directories. If you want to maintain a simple file name contract with the outside world, you could have the base file class figure out if the given file name is actually a directory and pass back an object of the derived directory class if it is.

    I've had similar interfaces where the decision can't be made at construction time. In the file example, maybe you first have an object representing a remote file on another system, but it's not until some connect method that more details of that object are filled in and you realize the remote file is actually a directory. I've morphed objects into objects of derived classes in cases like that by reblessing them.

    I don't consider this bad coding practice since you've maintained the contract, at least in a loose sense: The object is still a file, it's just a more specific kind of file. There are obvious flaws in this design, but in some cases I've found it to be the best choice, given that any approach has flaws.