-->

Perl6: Constructors in subclases

2019-06-21 11:57发布

问题:

Is there a way to assign instance variables declared in a super class, from a constructor in a sub class? I have gotten used to using BUILD() as constructor, but I am wondering if this is a good idea. I.e:

use v6;      

class File                                                                                                                                                                                                                                    
{                                                                                                                                                                                                                                             
    has $!filename;                                                                                                                                                                                             
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

class XmlFile is File                                                                                                                                                                                                                         
{                                                                                                                                                                                                                                             
    submethod BUILD(:$!filename)                                                                                                                                                                                                              
    {
    }
}

my XmlFile $XF = XmlFile.new(filename => "test.xml");

The code above doesn’t work, prompting an error: "Attribute $!filename not declared in class XmlFile". Is it a matter of using the right accesser? Changing "!" to "." does not solve the problem.

回答1:

You're halfway there. You have to make the correct two changes to your code:

class File {
    has $.filename;                   # 1. Replace `!` with `.`
}

class XmlFile is File {
    submethod BUILD(:$filename) { }   # 2. Remove `!`
}

dd my XmlFile $XF = XmlFile.new(filename => "test.xml");

# XmlFile $XF = XmlFile.new(filename => "test.xml")

Replacing $!filename with $.filename in the File class generates a public accessor method (.filename) in that class.

(Note that attributes are technically always private to a class, i.e. always unavailable to other classes, even trusted ones. When you see the phrase "public attribute" it really means there's a "public accessor" that controls access to a corresponding underlying private attribute.)

Removing the ! twigil from the BUILD signature in the XmlFile class means you're no longer trying to reference a non-existent XmlFile attribute and instead just passing a named argument.

Per Object Construction:

Due to the default behavior of BUILDALL and BUILD submethods, named arguments to the constructor new derived from Mu can correspond directly to public attributes of any of the classes in the method resolution order, or to any named parameter of any BUILD submethod.

(There's that "public attribute" misnomer. It means "attributes with a matching public accessor".)



回答2:

If you would like it to remain private, you can always add it directly to the subclass as well. (Perhaps you don't control the class you are subclassing?) You will, of course, have to be careful about which methods do which things between your class and subclass.

class File                                                                                                                                                                                                                                    
{                                                                                                                                                                                                                                             
    has $!filename;                                                                                                                                                                                             
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

class XmlFile is File                                                                                                                                                                                                                         
{   
    has $!filename;

    submethod BUILD(:$!filename)                                                                                                                                                                                                              
    {
    }
}

my XmlFile $XF = XmlFile.new(filename => "test.xml");

You may also want to consider using a 'has a' relationship instead of a 'is a' relationship by making an attribute of class XmlFile that holds a class File depending on what you are trying to do.

The handles trait makes delegation to another class a particularly easy and useful option to direct subclassing.

class XmlFile
{
    has File $!file handles<some methods>;
    ...
}