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

佐手、 提交于 2019-12-05 17:40:42

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

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).

You could make the users only get weakrefs to the object, with the single strong reference kept inside your module. Then when the resource is destroyed, delete the strong reference and poof, no more objects.

draegtun

Following on from the comments in my first answer here is "one way" to amend an object behaviour with Moose.

{
    package ExternalResource;
    use Moose;
    with 'DefaultState';
    no Moose;
}

{
    package DefaultState;
    use Moose::Role;

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

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

    no Moose::Role;
}

{
    package DeletedState;
    use Moose::Role;

    sub meth1 { print "meth1 no longer available!\n" }
    sub meth2 { print "meth2 no longer available!\n" }

    no Moose::Role;
}

my $er = ExternalResource->new;
$er->meth1;     # => "in meth1"
$er->meth2;     # => "in meth2"

DeletedState->meta->apply( $er );
my $er2 = ExternalResource->new;
$er2->meth1;    # => "in meth1"  (role not applied to $er2 object)
$er->meth1;     # => "meth1 no longer available!"
$er->meth2;     # => "meth2 no longer available!"

DefaultState->meta->apply( $er );
$er2->meth1;    # => "in meth1"
$er->meth1;     # => "in meth1"
$er->meth2;     # => "in meth2"

There are other ways to probably achieve what you after in Moose. However I do like this roles approach.

Certainly food for thought.

draegtun

With Moose you can alter the class using its MOP underpinnings:

package ExternalResource;
use Moose;
use Carp;

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

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

sub delete {
    my $self = shift;
    my %copy;   # keeps copy of original subref
    my @methods = grep { $_ ne 'meta' } $self->meta->get_method_list;

    for my $meth (@methods) {
        $copy{ $meth } = \&$meth;
        $self->meta->remove_method( $meth );
        $self->meta->add_method( $meth => sub {
            carp "can't call $meth on object because it has been deleted";
            return 0;
        });
    }

    $self->meta->add_method( undelete => sub {
        my $self = shift;
        for my $meth (@methods) {
            $self->meta->remove_method( $meth );
            $self->meta->add_method( $meth => $copy{ $meth } );
        }
        $self->meta->remove_method( 'undelete' );
    });
}

Now all current and new instances of ExternalResource will reflect whatever the current state is.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!