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

末鹿安然 提交于 2021-02-07 11:43:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!