12

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

4

1 回答 1

14

有两种方法可以访问类型的私有方法,以及一种获取私有属性的方法。除了第一种调用私有方法的方法外,所有方法都需要元编程,无论如何,它的解释仍然涉及元编程。

例如,我们将实现一个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::ParametricRoleHOWand Metamodel::ConcreteRoleHOW(roles), and Metamodel::EnumHOW(enums) 是 Rakudo 内置的 HOWs 来做这个角色。的方法之一Metamodel::PrivateMethodContainerfind_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::ParametricRoleHOWMetamodel::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/,它允许您编写多行字符串。添加cbefore:to允许将闭包嵌入到字符串中。

为什么语法类是扩展的?

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

你说 ^find_private_method 和 ^get_attribute_for_usage 将一个对象作为它们的第一个参数,但你在示例中省略了它。为什么?

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

于 2019-09-17T22:53:09.017 回答