如何从它们所属的类型之外访问私有方法或属性?

How do you access private methods or attributes from outside the type they belong to?

在极少数情况下这实际上是可以接受的,例如在单元测试中,您可能想要获取或设置私有属性的值,或者调用不应该执行的类型的私有方法.真的不可能吗?如果没有,你怎么能做到?

您可以通过两种方式访问​​某个类型的私有方法,以及一种获取私有属性的方式。都需要元编程,除了第一种方式调用私有方法,反正解释还是元编程。

例如,我们将实现 Hidden class 使用私有属性隐藏值,以及 Password class 使用 Hidden 存储密码。 请勿将此示例复制到您自己的代码中。这不是您合理处理密码的方式;这只是为了举例。

调用私有方法

信任其他 classes

Metamodel::Trusting 是实现高阶工作所需行为的元角色(类型的类型,或种类,从这里开始称为 HOW)能够信任其他类型。 Metamodel::ClassHOW(classes,并通过扩展,语法)是 Rakudo 内置的唯一执行此角色的方法。

trusts 是一个关键字,可以在包内使用它来允许另一个包调用它的私有方法 (这不包括私有属性)。例如,使用 trusts:

粗略地实现密码容器 class 可能看起来像这样
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::MethodContainerMetamodel::MultiMethodContainer 是实现方法行为的其他元角色,但此处不予讨论。 Metamodel::ClassHOW(classes,通过扩展,语法)、Metamodel::ParametricRoleHOWMetamodel::ConcreteRoleHOW(角色)和 Metamodel::EnumHOW(枚举)是 Rakudo 内置的 HOW做这个角色。 Metamodel::PrivateMethodContainer 的方法之一是 find_private_method,它以一个对象和一个方法名称作为参数,当找到 none 时,或者 returns Mu,或者Method 表示您正在查找的方法的实例。

密码示例可以重写为不使用 trusts 关键字,方法是删除使 [​​=13=] 信任 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(classes,并通过扩展,语法),Metamodel::ParametricRoleHOWMetamodel::ConcreteRoleHOW(角色),Metamodel::EnumHOW(枚举)和 Metamodel::DefiniteHOW(在内部使用,因为值 self 绑定到 public 属性的访问器方法中)执行此角色。

添加到 HOW 的元方法之一 Metamodel::AttributeContainerget_attribute_for_usage,给定对象和属性名称,如果没有找到属性则抛出,否则 returns Attribute 表示您正在查找的属性的实例。

Attribute 是 Rakudo 内部存储属性的方式。我们在这里关心的 Attribute 的两个方法是 get_value,它接受一个包含 Attribute 实例和 returns 它的值的对象,以及 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"
#

F.A.Q.

{ ... } 是做什么的?

这会存根一个包,允许您在实际定义它之前声明它。

qc:to/END/ 是做什么的?

您可能以前见过 q:to/END/,它允许您编写多行字符串。在 :to 之前添加 c 允许在字符串中嵌入闭包。

为什么语法 class 是扩展名?

语法使用 Metamodel::GrammarHOW,它是 Metamodel::ClassHOW 的子class。

你说^find_private_method和^get_attribute_for_usage第一个参数是一个对象,你在例子中省略了。为什么?

在对象上调用元方法将其自身作为第一个参数隐式传递。如果我们直接在对象的 HOW 上调用它们,我们会将对象作为第一个参数传递。