4

我正在编写一个链接到外部资源的类。其中一种方法是删除外部资源的删除方法。不应对该对象进行进一步的方法调用。如果设置了标志,我正在考虑设置一个标志并在所有方法中消亡,但有没有更好、更简单的方法?可能涉及到 DESTROY 的东西?

到目前为止,我真的很喜欢 Axeman 的建议,但是使用 AUTOLOAD 因为我懒得重新创建所有方法:

#!/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;
}
4

5 回答 5

6

简单地考虑处于无效状态的对象是否存在问题。如果用户坚持下去,那不是他们的问题吗?

以下是一些注意事项:

  • 您是否已经决定是否值得死去?

  • 很有可能,如果您有一个足够封装的函数,您真的不希望用户解析您的代码。为此,您可能不想使用我所说的 Go-ahead-and-let-it-fail 模式。'Can't call method "do_your_stuff" on an undefined value'可能不适用于封装目的。除非你告诉他们“嘿,你删除了对象!

以下是一些建议:

  • 您可以将对象重新分配到一个唯一的工作是指示无效状态的类中。它具有相同的基本形式,但表中的所有符号都指向一个仅表示“抱歉不能这样做,我已被关闭(你关闭我,记得吗?)”的子项。

  • 你可以$_[0]在删除中取消定义。然后他们'Can't call method "read_from_thing" on an undefined value'从代码中的一行得到一个不错的结果——前提是他们没有经过精心的装饰或委托过程。但是正如混乱所指出的那样,这并不能清除多个参考(正如我通过下面的示例代码改编的那样)。


一些概念证明的东西:

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
于 2009-05-01T21:10:25.707 回答
2

理想情况下,它应该超出范围。如果由于某种原因无法界定适当的范围,并且您担心引用意外地保持资源活动,则可能会考虑弱引用(Scalar::Util 至少在 5.10 中是核心)。

于 2009-05-01T21:13:31.067 回答
2

您可以让用户只获得对象的引用,而将单个强引用保存在模块中。然后当资源被销毁时,删除强引用和噗,不再有对象。

于 2009-05-01T21:14:50.657 回答
1

继我在这里的第一个答案中的评论之后,使用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"

还有其他方法可以实现您在 Moose 中所追求的目标。但是我确实喜欢这种角色方法。

当然值得深思。

于 2009-05-03T18:20:58.670 回答
0

使用Moose ,您可以使用其MOP基础来更改类:

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' );
    });
}

现在 ExternalResource 的所有当前和新实例都将反映当前状态。

于 2009-05-03T11:15:18.833 回答