在一些实际上可以接受的极少数情况下,例如在单元测试中,您可能想要获取或设置私有属性的值,或者调用不应该的类型的私有方法。真的不可能吗?如果没有,你怎么能做到?
1 回答
有两种方法可以访问类型的私有方法,以及一种获取私有属性的方法。除了第一种调用私有方法的方法外,所有方法都需要元编程,无论如何,它的解释仍然涉及元编程。
例如,我们将实现一个Hidden
使用私有属性隐藏值的Password
类,以及一个Hidden
用于存储密码的类。不要将此示例复制到您自己的代码中。这不是您合理处理密码的方式;这仅仅是为了举例。
调用私有方法
信任其他类
Metamodel::Trusting
是实现高阶工作(类型的类型或种类,以下称为 HOW)所需的行为以能够信任其他类型的元角色。Metamodel::ClassHOW
(类,以及通过扩展,语法)是唯一内置到 Rakudo 中的 HOW 执行此角色。
trusts
是一个关键字,可以在包内使用以允许另一个包调用其私有方法(这不包括私有属性)。例如,密码容器类的粗略实现可能如下所示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"
#
使用 ^find_private_method 元方法
Metamodel::PrivateMethodContainer
是一个元角色,它实现了应该能够包含私有方法的 HOW 的行为。Metamodel::MethodContainer
并且Metamodel::MultiMethodContainer
是实现方法行为的其他元角色,但这里不会讨论这些。Metamodel::ClassHOW
(classes, and by extensions, grammars), Metamodel::ParametricRoleHOW
and Metamodel::ConcreteRoleHOW
(roles), and Metamodel::EnumHOW
(enums) 是 Rakudo 内置的 HOWs 来做这个角色。的方法之一Metamodel::PrivateMethodContainer
是find_private_method
,它将一个对象和一个方法名称作为参数,并且Mu
在没有找到时返回,或者Method
代表您正在查找的方法的实例。
密码示例可以重写为不使用trusts
关键字,方法是删除Hidden
信任的行Password
并将其更改Password!dump
为:
method !dump(Password:D: --> Str:D) {
my Method:D $dump = $!value.^find_private_method: 'dump';
qc:to/END/;
{self.^name}:
$!value: {$dump($!value)}
END
}
获取和设置私有属性
Metamodel::AttributeContainer
是实现应该包含属性的类型的行为的元角色。与方法不同,这是处理所有类型属性所需的唯一元角色。在 Rakudo 内置的 HOW 中,Metamodel::ClassHOW
(类,以及扩展的语法)Metamodel::ParametricRoleHOW
和Metamodel::ConcreteRoleHOW
(角色)、Metamodel::EnumHOW
(枚举)和Metamodel::DefiniteHOW
(在内部使用,因为值self
在公共属性的访问器方法中绑定)执行此角色。
Metamodel::AttributeContainer
添加到 HOW的元方法之一是get_attribute_for_usage
,它给定一个对象和一个属性名称,如果没有找到属性则抛出,否则返回Attribute
代表您正在查找的属性的实例。
Attribute
是 Rakudo 内部存储属性的方式。Attribute
我们在这里关心的两个方法是get_value
,它接受一个包含Attribute
实例的对象并返回其值,以及set_value
,它接受一个包含Attribute
实例和值的对象,并设置它的值。
密码示例可以重写,因此Hidden
不会dump
像这样实现私有方法:
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"
#
常问问题
做什么{ ... }
?
这会存根一个包,允许您在实际定义它之前声明它。
做什么qc:to/END/
?
您可能以前见过q:to/END/
,它允许您编写多行字符串。添加c
before:to
允许将闭包嵌入到字符串中。
为什么语法类是扩展的?
语法使用Metamodel::GrammarHOW
,它是 的子类Metamodel::ClassHOW
。
你说 ^find_private_method 和 ^get_attribute_for_usage 将一个对象作为它们的第一个参数,但你在示例中省略了它。为什么?
在对象上调用元方法将其自身作为第一个参数隐式传递。如果我们直接在对象的 HOW 上调用它们,我们会将对象作为第一个参数传递。