如何隐藏用户的"绑定"调用,以便调用访问者将隐式为他们执行此操作?
我想这样做,因为我有一个可以由用户访问的数据结构,但是可以在用户不知情的情况下修改存储在该结构中的值.
如果数据结构中的属性发生更改,我希望修改引用该属性的任何变量,以便用户始终使用新数据.由于用户总是想要新鲜数据,如果用户甚至不需要知道它正在发生,那么它更简单,更直观.
这是我到目前为止...它似乎没有工作,但输出是:
hello
hello
我想要的是:
hello
goodbye
码:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{
    package File;
    use Moose;
    has '_text' => (is => 'rw', isa => 'Str', required => 1);
    sub text {
        my ($self) = @_;
        tie my $text, 'FileText', $self;
        return $text;
    }
}
{
    package FileText;
    use Tie::Scalar;
    sub TIESCALAR {
        my ($class, $obj) = @_;
        return bless \$obj, $class;
    }
    sub FETCH {
        my ($self) = @_;
        return $$self->_text();
    }
    sub STORE {
        die "READ ONLY";
    }
}
my $file = 'File'->new('_text' => 'hello');
my $text = $file->text();
say $text;
$file->_text('goodbye');
say $text;
我不建议这样做.你正在引入"远距离行动",这导致一些非常难以捕捉的错误.用户认为他们正在收到一个字符串.词法字符串只能通过直接明显地改变它来改变.它必须在适当的位置进行更改或明显地传递到附加到某事物的函数或引用中.
my $text = $file->text;
say $text;  # let's say it's 'foo'
...do some stuff...
$file->text('bar');
...do some more stuff...
# I should be able to safely assume it will still be 'foo'
say $text;
这段代码很容易理解,因为所有可能影响的东西都可以$text立即看到.这就是词汇上下文的全部内容,隔离可以改变变量的内容.
通过返回一个可以随时改变的东西,你已经悄然打破了这个假设.用户没有迹象表明假设被打破了.当他们去打印$text并获得bar它是不明显的变化$text.整个计划中的任何内容都可能发生变化$text.那小块代码现在变得无比复杂.
另一种看待它的方法是:Perl中的标量变量具有定义的接口.该界面的一部分说明了如何更改它们.您正在破坏此界面并向用户撒谎.这就是过载/绑定变量通常被滥用的方式.
无论你想要解决什么问题,你都可以通过增加更多问题来解决它,使代码更加复杂和难以理解.我会退后一步,问一下你试图解决的问题.
我要做的只是返回一个标量参考.这会警告用户可以随时从其下面更改.没有任何魔法可以掩盖非常重要的信息.
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{
    package File;
    use Moose;
    has 'text_ref' => (
        is              => 'rw',
        isa             => 'Ref',
        default         => sub {
            return \("");
        }
    );
    sub BUILDARGS {
        my $class = shift;
        my %args  = @_;
        # "Cast" a scalar to a scalar ref.
        if( defined $args{text} ) {
            $args{text_ref} = \(delete $args{text});
        }
        return \%args;
    }
    sub text {
        my $self = shift;
        if( @_ ) {
            # Change the existing text object.
            ${$self->text_ref} = shift;
            return;
        }
        else {
            return $self->text_ref;
        }
    }
}
my $file = 'File'->new('text' => 'hello');
my $text = $file->text();
say $$text;
$file->text('goodbye');
say $$text;
也就是说,这就是你如何做你想做的事.
我建议不要使用领带.它非常慢,比方法调用,错误和古怪慢得多.它的一个怪癖是绑定的性质附加到变量本身,而不是引用的数据.这意味着您无法返回绑定变量.
相反,我建议使用重载对象来存储更改的文本.
{
    package ChangingText;
    # Moose wants class types to be in a .pm file.  We have to explciitly
    # tell it this is a class type.
    use Moose::Util::TypeConstraints qw(class_type);
    class_type('ChangingText');
    use overload
      '""' => sub {
          my $self = shift;
          return $$self;
      },
      fallback => 1;
    sub new {
        my $class = shift;
        my $text = shift;
        return bless \$text, $class;
    }
    sub set_text {
        my $self = shift;
        my $new_text = shift;
        $$self = $new_text;
        return;
    }
}
重载对象有各自的警告,主要是由于代码需要字符串编写类似的东西if !ref $arg,但它们比深层绑定错误更容易处理.
要使其透明,请将ChangingText对象存储在File对象中,然后text在其周围放置一个手工制作的访问器来处理纯字符串.访问者确保重用相同的ChangingText对象.
为了完成这种错觉,BUILDARGS用于将纯文本初始化参数更改为ChangingText对象.
{
    package File;
    use Moose;
    has 'text_obj' => (
        is              => 'rw',
        isa             => 'ChangingText',
        default         => sub {
            return ChangingText->new;
        }
    );
    sub BUILDARGS {
        my $class = shift;
        my %args  = @_;
        # "Cast" plain text into a text object
        if( defined $args{text} ) {
            $args{text_obj} = ChangingText->new(delete $args{text});
        }
        return \%args;
    }
    sub text {
        my $self = shift;
        if( @_ ) {
            # Change the existing text object.
            $self->text_obj->set_text(shift);
            return;
        }
        else {
            return $self->text_obj;
        }
    }
}
然后它透明地工作.
my $file = File->new('text' => 'hello');
my $text = $file->text();
say $text;  # hello
$file->text('goodbye');
say $text;  # goodbye