2

如何向用户隐藏“领带”调用,以便调用访问器会隐含地为他们执行此操作?

我想这样做,因为我有一个用户可以访问的数据结构,但是存储在这个结构中的值可以在用户不知情的情况下被修改。

如果数据结构中的属性发生变化,我希望引用该属性的任何变量也被修改,这样用户将始终使用新数据。由于用户总是想要新鲜的数据,如果用户甚至不需要知道它正在发生,它会更简单和更直观。

这就是我到目前为止所拥有的......虽然它似乎不起作用,输出是:

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;
4

2 回答 2

5

我不建议这样做。您正在引入“远距离行动”,这会导致一些非常难以捕捉的错误。用户认为他们得到了一个字符串。一个词串只能通过直接而明显地改变它来改变。它必须在适当的位置进行更改,或者显然传递给函数或附加到某物的引用。

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在其周围放置一个手工制作的访问器来处理纯字符串。访问器确保重用相同的 ChangeText 对象。

为了完成这种错觉,BUILDARGS 用于将纯文本初始化参数更改为 ChangeText 对象。

{
    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
于 2015-08-06T17:43:03.423 回答
2

return $text只返回变量的值,而不是变量本身。但是,您可以返回对它的引用:

sub text {
    my ($self) = @_;
    tie my $text, 'FileText', $self;
    return \$text;
}

然后你必须使用$$text它来取消引用它:

my $file = 'File'->new('_text' => 'hello');

my $text = $file->text();
say $$text;

$file->_text('goodbye');
say $$text;
于 2015-08-06T17:14:56.883 回答