Κληρονομικότητα και επίλυση μεθόδων#
Η αναζήτηση μεθόδων στην Perl είναι απλή στην περίπτωση απλής κληρονομικότητας και γνήσια λεπτή μόλις μπουν στο παιχνίδι ρόμβοι ή μεικτά συστήματα κλάσεων. Αυτό το κεφάλαιο καλύπτει την πλήρη ιστορία: τους αλγορίθμους MRO, το SUPER::, τον τελεστή isa, το can, το DOES και την αλληλεπίδραση μεταξύ κλασικών και σύγχρονων κλάσεων σε μία ιεραρχία.
Ο αλγόριθμος αναζήτησης#
Όταν γράφετε $obj->foo(@args), η Perl κάνει τα εξής:
Προσδιορίζει την κλάση. Για ένα αντικείμενο, είναι η κλάση στην οποία έγινε bless ο αναφερόμενος· για κλήση μεθόδου κλάσης, είναι ο ίδιος ο επικαλούμενος.
Διασχίζει τη σειρά επίλυσης μεθόδων (MRO) της κλάσης κατά σειρά. Για κάθε πακέτο στη σειρά, αναζητά μια sub με το όνομα
foo.Η πρώτη αντιστοιχία κερδίζει. Αν δεν βρεθεί αντιστοιχία σε καμία κλάση του MRO, ξαναδιασχίζει αναζητώντας
AUTOLOAD.Αν δεν βρεθεί ούτε
AUTOLOAD, εκπέμπειCan't locate object method "foo" via package "Class".
Το MRO είναι αυτό στο οποίο γραμμικοποιείται η αλυσίδα @ISA της κλάσης. Ο κανόνας γραμμικοποίησης εξαρτάται από το ποιο MRO είναι σε ισχύ.
Τα δύο MRO: dfs και c3#
Η Perl παρέχει δύο αλγορίθμους MRO:
dfs- αναζήτηση κατά βάθος του@ISA. Η προεπιλογή. Διασχίζει κάθε γονέα και τους γονείς κάθε γονέα, από αριστερά προς τα δεξιά, βυθιζόμενη πλήρως σε κάθε υποδέντρο πριν προχωρήσει.c3- η γραμμικοποίηση C3 που χρησιμοποιούν η Python, η Raku και πολλές άλλες. Εγγυάται μονοτονικότητα: μια υποκλάση δεν εμφανίζεται ποτέ αργότερα από κάποιον πρόγονό της στη σειρά αναζήτησης.
Στην απλή κληρονομικότητα οι δύο είναι ταυτόσημες: η λίστα ανεβαίνει ευθεία στην αλυσίδα.
Στην πολλαπλή κληρονομικότητα αποκλίνουν. Σκεφτείτε τον ρόμβο:
A
/ \
B C
\ /
D
dfs:D, B, A, C, A(συμπτυσσόμενη σεD, B, A, C). Μια μέθοδος στοCπου υπερκαλύπτει τοAεπικαλύπτεται από τοAεπειδή τοAεπιτυγχάνεται πρώτα μέσω τουB.c3:D, B, C, A. Η υπερκάλυψη τουCείναι ορατή.
Το C3 είναι σχεδόν πάντα αυτό που θέλετε υπό πολλαπλή κληρονομικότητα. Επιλέξτε το ανά κλάση:
use mro 'c3';
package Diamond::D;
our @ISA = ('Diamond::B', 'Diamond::C');
Για κλάσεις δηλωμένες με class, η πολλαπλή κληρονομικότητα δεν υποστηρίζεται καθόλου, οπότε η επιλογή MRO είναι αμιγώς μια γραμμική διάσχιση και η προεπιλογή dfs είναι μια χαρά.
SUPER::#
Το $self->SUPER::foo(@args) καλεί τη foo σε μια από τις γονικές κλάσεις του πακέτου όπου μεταγλωττίστηκε η τρέχουσα μέθοδος. Αυτή η τελευταία φράση είναι αυτή που όλοι λανθάνουν.
package Animal;
sub speak { 'some sound' }
package Dog;
our @ISA = ('Animal');
sub speak { 'woof' }
package Puppy;
our @ISA = ('Dog');
sub speak {
my $self = shift;
return $self->SUPER::speak;
}
Η Puppy::speak καλεί την SUPER::speak. Η Perl αναζητά από το Puppy (το πακέτο μεταγλώττισης), όχι από το ref($self). Η αναζήτηση ξεκινά στο @ISA του Puppy, οπότε βρίσκει την Dog::speak και επιστρέφει 'woof'.
Αυτό έχει σημασία όταν οι μέθοδοι κληρονομούνται και το παιδί καλεί από μέσα το SUPER::. Επειδή το SUPER:: αγκυρώνεται στο πακέτο μεταγλώττισης, μια μέθοδος κληρονομημένη από το Dog και καλούμενη σε αντικείμενο Puppy θα εξακολουθήσει να καλεί το Animal:: μέσω του δικού της SUPER::, όχι το Dog. Συνήθως αυτή είναι η σωστή συμπεριφορά· όταν δεν είναι, στραφείτε στη ρητή πλοήγηση της αλυσίδας με next::method, που καλύπτεται παρακάτω.
next::method - το SUPER με επίγνωση του MRO#
Η mro::next::method (εκτεθειμένη ως $self->next::method(@args)) διασχίζει το ζωντανό MRO από την πραγματική θέση κλήσης της τρέχουσας μεθόδου, όχι από το πακέτο μεταγλώττισης:
use mro 'c3';
sub speak {
my $self = shift;
return $self->next::method;
}
Υπό C3 με ρόμβο, η next::method είναι αυτό που θέλετε· το SUPER:: μπορεί να παρακάμψει έναν αδελφικό κλάδο επειδή συμβουλεύεται μόνο τον αριστερότερο πρόγονο.
Ο τελεστής isa#
Ο τελεστής isa ελέγχει την ιδιότητα μέλους κλάσης χωρίς την παγίδα της κλήσης UNIVERSAL::isa ως συνάρτησης:
if ($obj isa 'File::MP3') { ... }
if ($obj isa File::MP3) { ... } # bareword form
Τόσο οι κλάσεις βασισμένες σε bless όσο και εκείνες που δηλώνονται με class απαντούν σωστά στο isa, και οι σχέσεις υποκλάσεων ρέουν μέσω των :isa και @ISA πανομοιότυπα.
Τρεις κανόνες που αξίζει να θυμάστε:
Το
isaεπιστρέφει ψευδές (όχι σφάλμα) όταν του δοθεί μη αντικείμενο. Το42 isa Fooείναι ψευδές. Τοundef isa Fooείναι ψευδές.Το
isaσέβεται το ζωντανό MRO τη στιγμή της κλήσης. Αν κώδικας προσθέσει στοιχεία στο@ISAκατά τον χρόνο εκτέλεσης, τοisaτα βλέπει. (Αυτός είναι άλλος ένας λόγος που η pperl απορρίπτει τον χειρισμό του@ISAγια κλάσεις δηλωμένες μεclass- οι εγγυήσεις θα γίνονταν θολές.)Προτιμήστε τον τελεστή
isaέναντι τουUNIVERSAL::isa($obj, 'Class'). Ο τελεστής βραχυκυκλώνει σε μη αντικείμενα αντί να πεθαίνει· η μορφή κλήσης συνάρτησης ηττάται επίσης από overloaded αντικείμενα.
can#
Το $obj->can('method') επιστρέφει μια αναφορά κώδικα αν το αντικείμενο έχει μέθοδο με αυτό το όνομα, διαφορετικά undef:
if (my $code = $obj->can('save')) {
$code->($obj, @args);
}
Χρήσεις:
Ανίχνευση χαρακτηριστικού πριν την κλήση: αποφεύγει το σφάλμα
Can't locate object method.Αποστολή σε μέθοδο με βάση το όνομα από συμβολοσειρά αποθηκευμένη σε μεταβλητή - πιο ευανάγνωστο από τη μορφή
$obj->$nameόταν η μέθοδος ενδέχεται να μην υπάρχει.
Μη-χρήσεις:
Μη χρησιμοποιείτε το
canγια να αποφασίσετε αν ένα αντικείμενο υλοποιεί έναν εννοιολογικό ρόλο. Μια κλάση μπορεί να ορίζει μια μέθοδοsaveπου κάνει κάτι εντελώς άσχετο με την μονιμοποίηση. Για ιδιότητα μέλους ρόλου, δείτε ρόλοι και ανάθεση.
DOES#
Το $obj->DOES('RoleOrClass') είναι το αντίστοιχο με επίγνωση ρόλων του isa. Από προεπιλογή το DOES αναθέτει στο isa, οπότε για κλάσεις χωρίς ρητή μηχανική ρόλων τα δύο συμπεριφέρονται το ίδιο. Τα συστήματα ρόλων (κλασικά Moose, Moo και το μελλοντικό χαρακτηριστικό ρόλων του πυρήνα) υπερκαλύπτουν το DOES ώστε να επιστρέφει αληθές και για ρόλους που καταναλώνει η κλάση.
if ($obj->DOES('Printable')) { ... }
Στην pperl, το DOES είναι η σωστή μέθοδος ελέγχου όταν θέλετε να ρωτήσετε «υλοποιεί αυτό το αντικείμενο το συμβόλαιο;» αντί για «κληρονομεί αυτό το αντικείμενο από αυτή την κλάση;».
Μεικτές κλασικές / σύγχρονες ιεραρχίες#
Οι κλάσεις δηλωμένες με class και εκείνες βασισμένες σε bless διαλειτουργούν και προς τις δύο κατευθύνσεις, υπό μια χούφτα κανόνες.
Μια σύγχρονη κλάση που κληρονομεί από κλασική κλάση#
package Shape; # classical
sub new {
my ($class, %args) = @_;
return bless { colour => $args{colour} // 'black' }, $class;
}
sub colour { $_[0]{colour} }
use feature 'class';
class Circle :isa(Shape) { # modern inherits classical
field $radius :param :reader;
method area { 3.14159265 * $radius ** 2 }
}
Αυτό λειτουργεί. Ο δημιουργημένος κατασκευαστής της σύγχρονης κλάσης αναθέτει στη new του γονέα, και οι μέθοδοι που κληρονομούνται από τον κλασικό γονέα εμφανίζονται στο σύγχρονο παιδί. Η κατάσταση των hash-slot του γονέα ζει δίπλα στην αποθήκευση πεδίων του παιδιού.
Επιφύλαξη: το σύγχρονο παιδί δεν μπορεί να φτάσει στο hash του γονέα. Το $self->{colour} δεν είναι θεμιτό μέσα σε μέθοδο της Circle· πρέπει να καλέσετε $self->colour.
Μια κλασική κλάση που κληρονομεί από σύγχρονη κλάση#
use feature 'class';
class Animal {
field $name :param :reader;
method speak { 'some sound' }
}
package Dog; # classical inherits modern
our @ISA = ('Animal');
sub new {
my ($class, %args) = @_;
return $class->SUPER::new(%args);
}
sub speak { 'woof' }
Και αυτό λειτουργεί - ο δημιουργημένος κατασκευαστής του σύγχρονου γονέα κληρονομείται, και το SUPER::new επιλύεται σε αυτόν. Το κλασικό παιδί δεν πρέπει να επιχειρήσει να τροποποιήσει απευθείας τα πεδία του γονέα· τα πεδία είναι ιδιωτικά στην κλάση που τα δήλωσε.
Τι αποτυγχάνει σε μεικτές ιεραρχίες#
Πρόσβαση
$self->{fieldname}πέρα από το σύνορο. Τα σύγχρονα πεδία δεν είναι hash-slot, και οι κλασικές hash-slot δεν είναι ορατές ως πεδία. Διαπερνάτε πάντα το σύνορο μέσω μεθόδου.Πολλαπλή κληρονομικότητα σε σύγχρονη κλάση. Το attribute
:isaπαίρνει μόνο έναν στόχο. Αν χρειάζεστε να αναμίξετε συμπεριφορά από πολλαπλά σημεία, δείτε ρόλοι και ανάθεση.Χειρισμός του
@ISAκατά τον χρόνο εκτέλεσης σε σύγχρονη κλάση. Απροσδιόριστη συμπεριφορά. Αντιμετωπίστε το@ISAως μόνο για ανάγνωση για κάθε κλάση δηλωμένη μεclass.
Γιατί η πολλαπλή κληρονομικότητα συνήθως βλάπτει#
Κάθε γλώσσα με πολλαπλή κληρονομικότητα τελικά ανακαλύπτει τις ίδιες παθολογίες. Η αναζήτηση μεθόδων γίνεται εξαρτημένη από τη σειρά· τα προβλήματα του ρόμβου σας αναγκάζουν να σκεφτείτε γραμμικοποίηση· η περίπτωση «κληρονόμησα δύο φορές από την ίδια ρίζα» μπερδεύει την αρχικοποίηση. Οι ρόλοι - σύνθεση συμπεριφοράς χωρίς σχέση κληρονομικότητας - λύνουν το υποκείμενο πρόβλημα κοινής χρήσης χωρίς τις παθολογίες.
Στην pperl το χαρακτηριστικό class απαγορεύει σκόπιμα την πολλαπλή κληρονομικότητα. Κλασικός κώδικας που τη χρησιμοποιεί θα έπρεπε να μεταβαίνει προς ένα μοντέλο ρόλων· το κεφάλαιο για τους ρόλους είναι η επόμενη στάση.
Περαιτέρω ανάγνωση#
class- πλήρης σύνταξη των δηλώσεων σύγχρονης κλάσης, συμπεριλαμβανομένου του:isa.bless- η πρωτογενής οντότητα κάτω από την κλασική κληρονομικότητα.ref- αναφέρει το όνομα της κλάσης μιας blessed αναφοράς.isa- ο προτιμώμενος τελεστής ιδιότητας μέλους κλάσης.Ρόλοι και ανάθεση - η εναλλακτική που βασίζεται στη σύνθεση έναντι της πολλαπλής κληρονομικότητας.
Μετάβαση - μετακίνηση μιας υπάρχουσας ιεραρχίας από κλασική σε σύγχρονη.