Is possible to access ATTR for Perl packages without defining a getter?

戏子无情 提交于 2021-02-07 20:13:54

问题


I am currently working on a Perl 5.8 project. I have package that looks like:

package Foo::Bar;

use strict;
use warnings;
use Class::Std;

our @EXPORT_SAFE = qw(Class::Std);

my %baz :ATTR;

sub BUILD {
  my ($self, $ident, $args) = @_;
  
  $baz{$ident} = $$args{something};
}

I am not exposing baz with any getter, but I would like to fetch it's content for unit testing purposes. There is any way to do it in Perl?

e.g. in Ruby you can some_instance.instance_variable_get("@#{name}")

Thank you very much in advance


回答1:


TL;DR

The answer in the general case is "it depends (but probably)".

The answer in your case is "no".

Detail

There is no fixed way to implement classes and objects in Perl. In most cases an object will be a blessed reference to a hash. In those cases, you could simply treat the object reference as a hash reference and look up the key directly. So, code like this:

my $baz = $obj->{baz};

However, you're not using the most common approach to building Perl objects, you're using Class::Std. And Class::Std does things rather differently. Class::Std uses an approach called "inside-out objects" which was popularised by Damian Conway in Object Oriented Perl and which was very fashionable for a while about twenty years ago.

In normal objects, you have a hash for each object. The keys are the attribute names and the values are the attribute values. With inside-out objects, each attribute has its own hash. The keys are the stringified references to an individual object and the value is the attribute's value for that object.

In effect, the "standard" approach is:

$object{attr} = value;

And the inside out approach is:

$attr{object} = value;

One of the big reasons why people liked inside-out objects is that the attribute hashes can be lexical variables stored in the class's source code. And that means they are truly private and cannot be accessed from outside of the class - except by using the provided accessor methods. Which is great if you want to force Perl to implement a far stronger kind of encapsulation. But terrible if you want to break through that encapsulation in the way that you do here.

So, no. In your case, you can't do this.




回答2:


Class::Std could easily provide ->instance_variable_get, at least for variables that were given a name.[1] But it doesn't.


In the absence of something provided by Class::Std, you could add the following to your class:

use Carp qw( croak );

sub instance_variable_get {
   my ($self, $name) = @_;

   my $pkg_glob = do {
      no strict qw( refs );
      *{ __PACKAGE__ . "::" }
   };

   exists($pkg_glob->{$name})
      or croak("Unknown attribute $name");

   my $hash = *{ $pkg_glob->{$name} }{HASH}
      or croak("Unknown attribute $name");

   return $hash->{ident($self)};
}

Note that this requires using our instead of my for the hashes.

This will allow you to use

$some_instance->instance_variable_get('baz')

Of course, if you're using our for the hashes, you could also use

$Foo::Bar::baz{ $some_instance->ident }

  1. Currently, :ATTR( :name<...> ), :ATTR( :init_arg<...> ), :ATTR( :get<...> ) and :ATTR( :set<...> ) provide a name for the attribute, though these all have other effects as well.


来源:https://stackoverflow.com/questions/63595446/is-possible-to-access-attr-for-perl-packages-without-defining-a-getter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!