Perl - 重载包/类属性?

Dig*_*Man -6 oop perl overloading getter-setter

我试图从PHP移植一些代码,基本归结为属性重载.也就是说,如果你试图获取或设置一个实际上没有被定义为类的一部分的类属性,它会将该信息发送给一个几乎可以用它做任何事情的函数.(在这种情况下,我想在放弃之前在类中搜索关联数组.)

但是,Perl与PHP有很大的不同,因为类已经是哈希.有没有什么方法可以将一些等效的__get()和应用__set()到Perl"类"中,它仍将完全封装在该包中,对于试图实际获取或设置属性的任何内容都是透明的?

编辑:解释这个的最好方法可能是向您显示代码,显示输出,然后显示我想要输出的内容.

package AccessTest;

my $test = new Sammich;   #"improper" style, don't care, not part of the question.

say 'bacon is: ' . $test->{'bacon'};
say 'cheese is: ' . $test->{'cheese'};

for (keys $test->{'moreProperties'}) {
 say "$_ => " . $test->{'moreProperties'}{$_};
}

say 'invalid is: ' . $test->{'invalid'};

say 'Setting invalid.';
$test->{'invalid'} = 'true';
say 'invalid is now: ' . $test->{'invalid'};

for (keys $test->{'moreProperties'}) {
 say "$_ => " . $test->{'moreProperties'}{$_};
}


package Sammich;

sub new
{
 my $className = shift;
 my $this = {
  'bacon' => 'yes',
  'moreProperties' => {
  'cheese' => 'maybe',
  'ham' => 'no'
  }
};

return bless($this, $className);
}
Run Code Online (Sandbox Code Playgroud)

目前输出:

bacon is: yes
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 11.
cheese is: 
cheese => maybe
ham => no
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 17.
invalid is: 
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
Run Code Online (Sandbox Code Playgroud)

现在,我只需要对Sammich进行修改,而不对初始的AccessTest包进行任何更改,这将导致:

bacon is: yes
cheese is: maybe
cheese => maybe
ham => no
invalid is: 0
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
invalid => true
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,期望的效果是'cheese'属性,因为它不是直接测试对象的一部分,而是从'moreProperties'哈希中获取.'invalid'会尝试同样的事情,但由于它既不是直接属性也不是'moreProperties',它会以任何编程方式运行 - 在这种情况下,我希望它只返回值0,没有任何错误或警告.在尝试设置 'invalid'属性时,它不会直接添加到对象中,因为它不存在,而是添加到'moreProperties'哈希中.

我希望这需要超过PHP中需要的六行左右,但由于这是一个非常重要的OOP概念,我完全希望Perl以某种方式处理它.

Joe*_*ger 10

正如我在评论中所说,这个问题很难解决的原因是你没有遵循面向对象编程的黄金规则,即封装.你怎么能期望拦截一个不是方法的电话?如果您的公开API由getter/setter组成,那么您可以使用AUTOLOAD方法拦截未知方法调用.如果你不这样做,你可以使用@ pilcrow的高贵建议使用绑定哈希(编辑:或@tchrist对overloadpragma 的英勇使用); 这仍然是对Perl灵活性的一种赞扬,而不是你的API.

为了更正确地做到这一点(是的,我看到你"要求"不修改API,如果你选择忽略这一点,那么请将此帖子称为未来读者的消息).

#!/usr/bin/env perl

use v5.10; # say
use strict;
use warnings;

use MooseX::Declare;
use Method::Signatures::Modifiers;

class Sammich {
  has 'bacon' => ( isa => 'Str', is => 'rw', default => 'yes' );
  has 'more' => ( 
    isa => 'HashRef', 
    is => 'rw', 
    default => sub{ {
      cheese => 'maybe',
      ham => 'no',
    } },
  );

  our $AUTOLOAD;
  method AUTOLOAD ($val?) {
    # get unknown method name
    my $attr = (split( /::/, $AUTOLOAD))[-1];

    my $more = $self->more;

    # see if that method name exists in "more"
    if (exists $more->{$attr}) {
      # if so, are there new values? then set
      if (defined $val) {
        $more->{$attr} = $val;
      } 
      # ... and return
      return $more->{$attr};
    }

    # attr does not exist, so set it or initialize it to 0
    $more->{$attr} = defined $val ? $val : 0;
    return $more->{$attr};
  }
}

# I don't care that you don't care
my $test = Sammich->new();

say 'bacon is: ' . $test->bacon;
say 'cheese is: ' . $test->cheese;

for (keys %{ $test->more }) {
 say "$_ => " . $test->more->{$_};
}

say 'invalid is: ' . $test->invalid;

say 'Setting invalid.';
$test->invalid( 'true' );
say 'invalid is now: ' . $test->invalid;

for (keys %{ $test->more }) {
 say "$_ => " . $test->more->{$_};
}
Run Code Online (Sandbox Code Playgroud)

有些人可能会说我的措辞很苛刻,也许就是这样.我试图帮助那些将会得到帮助的人,因此看到了一个大胆的信息

我只需要对Sammich进行修改,而不对初始的AccessTest包进行任何修改

然后要求Perl向你的心血来潮低头

我希望这需要超过PHP中需要的六行左右,但由于这是一个非常重要的OOP概念,我完全希望Perl以某种方式处理它.

太令人厌烦了.我希望未来的读者能够看到这是封装从长远来看有何帮助的案例.

  • 我非常同意你的观点,`$ foo - > {field}`违反了封装.你只是不用*做*对象,通常甚至不在类内,除了该属性的特定getter/setter之外.也就是说,但是你的问题"你怎么能期望拦截一个不是方法的调用?"当然有一个明显的答案:你通过`use overload'%{}'=> blah_blah_blah;`重载哈希解除引用.所有的爵士乐.我强烈建议只对中介方法访问对象属性. (2认同)

pil*_*row 6

更新到更新

(我收到匿名的downvotes,大概是为了教唆你误入歧途的方法.:))

为了清楚起见,你提出的问题是一个"XY问题",特别是从PHP到Perl错误翻译OO技术的工件.例如,如所提到的各处在这个问题,在Perl对象属性一般应被实现为直接访问散列(REF)的元件.这是错误的"X".

从一种语言跳到另一种语言不仅仅引入了语法上的差异.

更新

你可以用这样的东西完成你想要的东西:

package UnfortunateHack; {

use Tie::Hash;
our @ISA = qw(Tie::StdHash);

sub FETCH {
  my ($self, $key) = @_;
  return exists $self->{$key}
         ? $self->{$key}
         : $self->{moreProperties}->{$key};
}

}

...

package Sammich;

sub new {
  my $class = shift;

  tie my %this, 'UnfortunateHack';
  %this = ( bacon => 'yes',
            moreProperties => { ... } );

  bless \%this, $class;
}
Run Code Online (Sandbox Code Playgroud)

原始答案

如果我理解你的意图 - 拦截不一定定义的$obj->property调用TheClass::property- 那么在OO上下文中的perl的AUTOLOAD就会做你想要的.

来自链接的文档(perlobj):

如果调用类中不存在的方法,Perl将抛出错误.但是,如果该类或其任何父类定义了AUTOLOAD方法,则会调用该AUTOLOAD方法.

  • 你使用哈希直接违反封装而不是使用getter/setter.如果为定义的属性提供这些方法,那么AUTOLOAD方法正是这里所需要的. (3认同)
  • 为什么要对对象进行数据deref? (3认同)
  • @JoelBerger,对.一个答案是,"不要这样做,不要简单地在Perl中编写PHP OO,因为它不仅仅是语法差异." (2认同)