【问题标题】:Perl - Overloading package/class properties?Perl - 重载包/类属性?
【发布时间】:2012-06-08 13:12:54
【问题描述】:

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

然而,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 能够以某种方式处理它。

【问题讨论】:

  • Perl 类不是散列,它们只是包。 Perl 对象也不是散列,尽管它们通常是散列到一个包中。
  • 展示一点 PHP 代码怎么样?我不完全确定我理解你的意图。
  • it is a very important concept of OOP, I fully expect Perl to handle it somehow. 确实如此,但 getter/setter 方法 也是如此,正如@pilcrow 所提到的,可以使用绑定哈希来实现您想要的语法,但这并不能使OOP,事实上 Perl 可以满足这种需求,这归功于 Perl 的灵活性;您仍然应该检查为什么要强制不正确的用法正常工作。
  • 为什么你blessing Hash ref,当你只使用它作为一个Hash ref?请参阅perlobjperlootut 了解更多详情。
  • @DigitalMan,我看到您的编辑已被回滚。去吧,别理我。如果您是这样的专家,请务必为所欲为。我只是对你的语气感到愤怒。 Perl 提供了重载散列键访问的工具。其他用户不应尝试这样做,但应遵循标准的 OO 协议,因为它们允许正确继承并避免认知失调。我不认为我是上帝,远非如此;但是你的语气并没有引发富有成效的讨论。

标签: perl oop overloading getter-setter


【解决方案1】:

正如我在我的 cmets 中所说,这个问题很难解决的原因是您没有遵循面向对象编程的黄金法则之一,即封装。你怎么能期望拦截一个不是方法的调用呢?如果您公开的 API 由 getter/setter 组成,那么您可以使用 AUTOLOAD 方法拦截未知方法调用。如果你不这样做,你可以使用@pilcrow 使用绑定哈希的崇高建议(编辑:或@tchrist 英勇使用overload pragma);与您的 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 能够以某种方式处理它。

很讨厌。我希望未来的读者能将其视为封装从长远来看有帮助的一个案例。

【讨论】:

  • 我非常同意你的观点,$foo->{field} 违反了封装。你只是不这样做对象,通常甚至不在类中,除了在该属性的特定 getter/setter 中。就是说,但是您的问题是“您如何期望拦截不是方法的调用?”当然有一个明显的答案:你通过use overload '%{}' => blah_blah_blah; 和所有爵士乐重载哈希解引用。我强烈建议只使用中介方法访问对象属性。
  • @tchrist,是的,您也可以使用绑定哈希,但它们都不是 OO,正如 OP 明确要求的那样。
  • 我认为他做错了,我想你也是。
【解决方案2】:

更新到更新

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

为了清楚起见,您提出的问题是"XY Problem",特别是错误地将 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 不一定定义 TheClass::property 的调用——那么在 OO 上下文中 perl 的 AUTOLOAD 将满足您的需求。

来自链接的文档 (perlobj):

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

【讨论】:

  • 根据我的测试,如果尝试了无效的方法,AUTOLOAD会被调用,但是$obj->{'invalid'}不会触及它。
  • 您直接使用哈希而不是使用 getter/setter 违反了封装。如果你为定义的属性提供这些方法,那么这里就需要一个 AUTOLOAD 方法。
  • 你为什么要对一个对象进行数据引用?
  • @tchrist,我?符合 OP 要求。如果你想问 OP,好吧,我很仁慈地假设他是在某个不是他自己制造的地狱里。
  • @JoelBerger,对。一个答案是,“不要这样做,不要简单地用 Perl 编写 PHP OO,因为它不仅仅是语法上的差异。”
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    您可以在Object-Oriented Programming in Perl Tutorial 中查看代码示例。

    【讨论】:

      猜你喜欢
      • 2011-07-29
      • 1970-01-01
      • 2011-06-18
      • 2011-07-12
      • 1970-01-01
      • 1970-01-01
      • 2014-12-07
      • 2011-06-17
      • 2012-09-15
      相关资源
      最近更新 更多