-6

我试图从 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);
}

这当前输出:

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

现在,我只需要对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

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

我希望这比 PHP 中需要的六行多,但由于它是 OOP 的一个非常重要的概念,我完全希望 Perl 能够以某种方式处理它。

4

4 回答 4

10

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

为了更正确地执行此操作(是的,我看到您“要求”不要修改 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->{$_};
}

有人可能会说我在这里的措辞很苛刻,也许确实如此。我试图帮助那些将得到帮助的人,因此看到一条粗体消息,例如

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

然后要求 Perl 屈服于你的心血来潮

我希望这比 PHP 中需要的六行多,但由于它是 OOP 的一个非常重要的概念,我完全希望 Perl 能够以某种方式处理它。

很烦人。我希望未来的读者会将此视为一个案例,说明为什么封装从长远来看会有所帮助。

于 2012-06-10T04:38:42.627 回答
6

更新到更新

(我收到匿名的反对票,大概是因为教唆你的错误方法。:))

为了清楚起见,您提出的问题是“XY 问题”,特别是错误地将 OO 技术从 PHP 转换为 Perl 的产物。例如,正如在这个问题中提到的passim,perl 中的对象属性通常不应该实现为直接访问的 hash(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;
}

原始答案

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

从链接的文档(perlobj):

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

于 2012-06-08T13:27:40.047 回答
4

完全违反了封装。为了向您证明这一点,请注释掉blessfrom&Sammich::new以便它返回一个普通的哈希引用。

package Sammich;

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

  # don't even bother blessing it
  # return bless($this, $className);
}

获得所需内容的唯一方法是使用magic.

于 2012-06-10T15:50:28.420 回答
1

在 Perl 中,类不仅仅是散列,它们是建立在包上的,你可以在那里定义你想要的任何方法,并且它仍然封装在那个包/类中。

您可以在 Perl 教程中的面向对象编程中查看代码示例。

于 2012-06-08T13:21:50.640 回答