Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What should I do with an object that should no longer be used in Perl?

I am writing a class that is linked to an external resource. One of the methods is a delete method that destroys the external resource. No further method calls should be made on that object. I was thinking of setting a flag and die'ing inside of all of the methods if the flag is set, but is there a better, easier way? Something involving DESTROY maybe?

So far, I am really liking Axeman's suggestion, but using AUTOLOAD because I am too lazy to recreate all of the methods:

#!/usr/bin/perl

use strict;
use warnings;

my $er = ExternalResource->new;

$er->meth1;
$er->meth2;

$er->delete;

$er->meth1;
$er->meth2;

$er->undelete;

$er->meth1;
$er->meth2;

$er->delete;

$er->meth1;
$er->meth2;
$er->meth3;

package ExternalResource;

use strict;
use warnings;

sub new {
    my $class = shift;
    return bless {}, $class;
}

sub meth1 {
    my $self = shift;
    print "in meth1\n";
}

sub meth2 {
    my $self = shift;
    print "in meth2\n";
}

sub delete {
    my $self = shift;
    $self->{orig_class} = ref $self;
    return bless $self, "ExternalResource::Dead";
}

package ExternalResource::Dead;

use strict;
use Carp;

our $AUTOLOAD;
BEGIN {
our %methods = map { $_ => 1 } qw/meth1 meth2 delete new/;
}
our %methods;

sub undelete {
    my $self = shift;
    #do whatever needs to be done to undelete resource
    return bless $self, $self->{orig_class};
}

sub AUTOLOAD {
    my $meth = (split /::/, $AUTOLOAD)[-1];
    croak "$meth is not a method for this object"
        unless $methods{$meth};
    carp "can't call $meth on object because it has been deleted";
    return 0;
}
like image 704
Chas. Owens Avatar asked Dec 23 '22 11:12

Chas. Owens


2 Answers

Is there a problem with simply considering the object in an invalid state. If the users hang on to it, isn't that their problem?

Here are some considerations:

  • Have you already decided whether it's worth dying over?

  • Chances are that if you have a function that is encapsulated enough, you really don't want to have the users parse through your code. For that purpose, you probably wouldn't like to use what I call the Go-ahead-and-let-it-fail pattern. 'Can't call method "do_your_stuff" on an undefined value' probably won't work as well for encapsulation purposes. Unless you tell them "Hey you deleted the object!

Here are some suggestions:

  • You could rebless the object into a class whose only job is to indicate an invalid state. It has the same basic form, but all symbols in the table point to a sub that just says "Sorry can't do it, I've been shut down (You shut me down, remember?)."

  • You could undef $_[0] in the delete. Then they get a nice 'Can't call method "read_from_thing" on an undefined value' from a line in their code--provided that they aren't going through an elaborate decorating or delegation process. But as pointed out by chaos, this doesn't clear up more than one reference (as I've adapted by example code below to show).


Some proof of concept stuff:

use feature 'say';

package A;

sub speak { say 'Meow!'; }

sub done { undef $_[0]; }

package B;

sub new { return bless {}, shift; }

sub speak { say 'Ruff!' }

sub done { bless shift, 'A'; }

package main;

my $a = B->new();
my $b = $a;

$a->speak(); # Ruff!
$b->speak(); # Ruff!
$a->done();
$a->speak(); # Meow!
$b->speak(); # Meow! <- $b made the switch
$a->done();
$b->speak(); # Meow!
$a->speak(); # Can't call method "speak" on an undefined value at - line 28
like image 190
Axeman Avatar answered May 03 '23 02:05

Axeman


Ideally, it should fall out of scope. If, for some reason, a proper scope can't be deliniated, and you're worried about references accidentally keeping the resource active, might consider weak references (Scalar::Util is core in at least 5.10).

like image 42
Anonymous Avatar answered May 03 '23 01:05

Anonymous