【问题标题】:What should I do with an object that should no longer be used in Perl?我应该如何处理不应再在 Perl 中使用的对象?
【发布时间】:2009-05-01 21:01:03
【问题描述】:

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

【问题讨论】:

    标签: perl oop design-decisions


    【解决方案1】:

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

    以下是一些注意事项:

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

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

    以下是一些建议:

    • 您可以将对象rebless 放入一个唯一的工作是指示无效状态的类中。它具有相同的基本形式,但表中的所有符号都指向一个子组件,它只是说“抱歉不能这样做,我已经被关闭了(你把我关闭了,记得吗?)。”

    • 你可以在删除中取消$_[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
    

    【讨论】:

    • 我喜欢选项二,它使正常方法保持干净,而且我可以发出警告而不是死亡。类似于 package ClassName::Dead;使用鲤鱼; sub AUTOLOAD { car​​p "无法在对象上调用 $AUTOLOAD,因为它已被删除" }
    • 因为我已经进行了编辑,Chas 所说的“选项二”现在是“解决方案一”。
    • @Chas,有没有办法“复活”它?
    • 不,一旦外部资源被删除,就不会取消删除,但是在可以取消删除的情况下添加一个很容易(可以使用原始类的取消删除方法)。
    • 很有趣,但也没有办法重新创建具有大致相似功能的类似资源?
    【解决方案2】:

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

    【讨论】:

    • 由于您拥有 5.10,您可以运行一个名为 corelist 的新实用程序,它会告诉您是否/何时将模块添加到 Core Perl。在这种情况下,Scalar::Util 是在 5.007003(又名 5.7.3)中添加的,这意味着我们在 5.8.0 中获得了它。
    • 感谢您提醒我有关 corelist 的信息。我总是检查我的版本,以确保人们知道我熟悉什么;我应该检查一下。
    【解决方案3】:

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

    【讨论】:

    • 不会 undef $_[0] 完成同样的事情吗?在我的测试代码中确实如此。
    • undef $_[0] 只是破坏了对该对象的引用。其余部分保持不变。
    • 好点。你的方式只会绑架用户制作的每一个副本。
    • 这是一个很好的解决方案,但我认为可以从Axeman的解决方案返回的错误消息更有帮助。另外,我不希望他们调用方法(因为外部资源不会回答),但成员仍然有效(并且有用)。
    • 是的,Axeman 的 rebless 绝对是非常圆滑的。而且不知何故非常Perlish。 :)
    【解决方案4】:

    从我的第一个答案中的 cmets 开始,这里是使用 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 中所追求的目标。不过我确实喜欢这种roles 方法。

    当然值得深思。

    【讨论】:

      【解决方案5】:

      使用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 的所有当前和新实例都将反映当前状态。

      【讨论】:

      • 那很糟糕,删除一个资源不应该破坏所有资源。
      • 是的,我对此表示同情,因为不确定这是否对您有帮助。该代码可能对其他要求有用,因此我将发布另一个答案,说明如何修改对象。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-20
      • 2010-09-19
      • 2011-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多