问题
In some rare cases where this would actually be acceptable, like in unit tests, you may want to get or set the value of a private attribute, or call a private method of a type where it shouldn't be possible. Is it really impossible? If not, how can you do it?
回答1:
There are two ways you can access a private method of a type, and one way to get private attributes. All require meta-programming except for the first way to invoke private methods, whose explanation still involves meta-programming anyway.
As an example, we will be implementing a Hidden
class that hides a value using a private attribute, and a Password
class that uses Hidden
to store a password. Do not copy this example to your own code. This is not how you would reasonably handle passwords; this is solely for example's sake.
Calling private methods
Trusting other classes
Metamodel::Trusting
is the meta-role that implements the behaviour needed for higher-order workings (types of types, or kinds, referred to from hereon out as HOWs) to be able to trust other types. Metamodel::ClassHOW
(classes, and by extension, grammars) is the only HOW that's builtin to Rakudo that does this role.
trusts
is a keyword that can be used from within packages to permit another package to call its private methods (this does not include private attributes). For example, a rough implementation of a password container class could look like this using trusts
:
class Password { ... }
class Hidden {
trusts Password;
has $!value;
submethod BUILD(Hidden:D: :$!value) {}
method new(Hidden:_: $value) {
self.bless: :$value
}
method !dump(Hidden:D: --> Str:D) {
$!value.perl
}
}
class Password {
has Hidden:_ $!value;
submethod BUILD(Password:D: Hidden:D :$!value) {}
method new(Password:_: Str:D $password) {
my Hidden:D $value .= new: $password;
self.bless: :$value
}
method !dump(Password:D: --> Str:D) {
qc:to/END/;
{self.^name}:
$!value: {$!value!Hidden::dump}
END
}
method say(Password:D: --> Nil) {
say self!dump;
}
}
my Password $insecure .= new: 'qwerty';
$insecure.say;
# OUTPUT:
# Password:
# $!value: "qwerty"
#
Using the ^find_private_method meta-method
Metamodel::PrivateMethodContainer
is a meta-role that implements the behaviour for HOWs that should be able to contain private methods. Metamodel::MethodContainer
and Metamodel::MultiMethodContainer
are the other meta-roles that implement the behaviour for methods, but those won't be discussed here. Metamodel::ClassHOW
(classes, and by extension, grammars), Metamodel::ParametricRoleHOW
and Metamodel::ConcreteRoleHOW
(roles), and Metamodel::EnumHOW
(enums) are the HOWs builtin to Rakudo that do this role. One of Metamodel::PrivateMethodContainer
's methods is find_private_method
, which takes an object and a method name as parameters and either returns Mu
when none is found, or the Method
instance representing the method you're looking up.
The password example can be rewritten not to use the trusts
keyword by removing the line that makes Hidden
trust Password
and changing Password!dump
to this:
method !dump(Password:D: --> Str:D) {
my Method:D $dump = $!value.^find_private_method: 'dump';
qc:to/END/;
{self.^name}:
$!value: {$dump($!value)}
END
}
Getting and setting private attributes
Metamodel::AttributeContainer
is the meta-role that implements the behaviour for types that should contain attributes. Unlike with methods, this is the only meta-role needed to handle all types of attributes. Of the HOWs builtin to Rakudo, Metamodel::ClassHOW
(classes, and by extension, grammars), Metamodel::ParametricRoleHOW
and Metamodel::ConcreteRoleHOW
(roles), Metamodel::EnumHOW
(enums), and Metamodel::DefiniteHOW
(used internally as the value self
is bound to in accessor methods for public attributes) do this role.
One of the meta-methods Metamodel::AttributeContainer
adds to a HOW is get_attribute_for_usage
, which given an object and an attribute name, throws if no attribute is found, otherwise returns the Attribute
instance representing the attribute you're looking up.
Attribute
is how attributes are stored internally by Rakudo. The two methods of Attribute
we care about here are get_value
, which takes an object that contains the Attribute
instance and returns its value, and set_value
, which takes an object that contains the Attribute
instance and a value, and sets its value.
The password example can be rewritten so Hidden
doesn't implement a dump
private method like so:
class Hidden {
has $!value;
submethod BUILD(Hidden:D: :$!value) {}
method new(Hidden:_: $value) {
self.bless: :$value;
}
}
class Password {
has Hidden:_ $!value;
submethod BUILD(Password:D: Hidden:D :$!value) {}
method new(Password:_: Str:D $password) {
my Hidden:D $value .= new: $password;
self.bless: :$value
}
method !dump(Password:D: --> Str:D) {
my Attribute:D $value-attr = $!value.^get_attribute_for_usage: '$!value';
my Str:D $password = $value-attr.get_value: $!value;
qc:to/END/;
{self.^name}:
$!value: {$password.perl}
END
}
method say(Password:D: --> Nil) {
say self!dump;
}
}
my Password:D $secure .= new: 'APrettyLongPhrase,DifficultToCrack';
$secure.say;
# OUTPUT:
# Password:
# $!value: "APrettyLongPhrase,DifficultToCrack"
#
F.A.Q.
What does { ... }
do?
This stubs a package, allowing you to declare it before you actually define it.
What does qc:to/END/
do?
You've probably seen q:to/END/
before, which allows you to write a multiline string. Adding c
before :to
allows closures to be embedded in the string.
Why are grammars classes by extension?
Grammars use Metamodel::GrammarHOW
, which is a subclass of Metamodel::ClassHOW
.
You say ^find_private_method and ^get_attribute_for_usage take an object as their first parameter, but you omit it in the example. Why?
Calling a meta-method on an object passes itself as the first parameter implicitly. If we were calling them directly on the object's HOW, we would be passing the object as the first parameter.
来源:https://stackoverflow.com/questions/57982956/how-do-you-access-private-methods-or-attributes-from-outside-the-type-they-belon