问题
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 }
- 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