Κλασική αντικειμενοστρέφεια#
Πριν φτάσει το χαρακτηριστικό class στην 5.38, η αντικειμενοστρέφεια της Perl ήταν χτισμένη πάνω σε τρεις πρωτογενείς οντότητες: ένα πακέτο, μια αναφορά και τη συνάρτηση bless. Αυτές οι τρεις οντότητες υπάρχουν ακόμη - η σύγχρονη δεσμευμένη λέξη class κάθεται πάνω στην ίδια μηχανική - και κάθε βάση κώδικα πριν την 5.38, καθώς και το μεγαλύτερο μέρος του CPAN, τις χρησιμοποιεί ακόμη απευθείας.
Αυτό το κεφάλαιο είναι για την ανάγνωση και τη συντήρηση τέτοιου κώδικα. Αν γράφετε νέα κλάση, ξεκινήστε από το κεφάλαιο της σύγχρονης κλάσης.
Οι τρεις πρωτογενείς οντότητες#
Ένα πακέτο ορίζει έναν χώρο ονομάτων. Οποιοδήποτε πακέτο μπορεί να είναι κλάση· η διαφορά είναι αμιγώς στον τρόπο χρήσης του.
Μια αναφορά κρατάει τα δεδομένα του στιγμιότυπου. Η σύμβαση είναι μια αναφορά hash, αλλά οποιοσδήποτε τύπος αναφοράς λειτουργεί.
Το
blessετικετοποιεί τον αναφερόμενο με ένα όνομα κλάσης, έτσι ώστε η αποστολή μεθόδων μέσω της αναφοράς να αναζητά subs σε αυτό το πακέτο.
Αυτό είναι όλο το μοντέλο. Όλα τα υπόλοιπα - κατασκευαστές, accessors, κληρονομικότητα, ρόλοι - είναι σύμβαση χτισμένη από πάνω.
Μια ελάχιστη κλάση#
package File;
sub new {
my ($class, %args) = @_;
my $self = {
path => $args{path},
content => $args{content},
};
return bless $self, $class;
}
sub path { $_[0]{path} }
sub content { $_[0]{content} }
sub print_info {
my $self = shift;
print "This file is at $self->{path}\n";
}
1;
Βασικές συμβάσεις, καθεμιά κρίσιμη:
Το
sub newείναι ο κατασκευαστής. Είναι μια συνηθισμένη μέθοδος· η γλώσσα δεν διαθέτει δεσμευμένη λέξη κατασκευαστή. Από σύμβαση ονομάζεταιnewκαι επιστρέφει την blessed αναφορά.Το
my ($class, %args) = @_αποπακετάρει πρώτα τον επικαλούμενο και μετά τα ονομασμένα ορίσματα. Μια κλήση μεθόδου κλάσηςFile->new(path => ...)περνάει το όνομα της κλάσης ως πρώτο όρισμα.Το
bless $self, $classπροσαρτά την ετικέτα της κλάσης. Η χρήση του$class(του πραγματικού επικαλούμενου) αντί ενός σταθερά κωδικοποιημένου'File'είναι αυτό που καθιστά τον κατασκευαστή ασφαλή για κληρονομικότητα: μια υποκλάση που κληρονομεί τηnewθα κάνει bless στο όνομα της υποκλάσης.Το
returnσυνήθως παραλείπεται μετά τοblessεπειδή τοblessεπιστρέφει το πρώτο του όρισμα. Τοbless $self, $classως τελευταία έκφραση στη sub είναι ιδιωματικό. Εδώ το γράφουμε ρητά για ευκολία ανάγνωσης.
Accessor μέθοδοι με το χέρι#
Το μοντέλο δεν σας δίνει accessors· τους γράφετε εσείς. Η συμπυκνωμένη μορφή εκμεταλλεύεται το γεγονός ότι το $_[0] είναι ο επικαλούμενος:
sub path { $_[0]{path} }
sub content { $_[0]{content} }
Η μορφή με ονομασμένη παράμετρο είναι σαφέστερη και είναι αυτή που πρέπει να γράφετε όταν ο accessor κάνει οτιδήποτε μη τετριμμένο:
sub path {
my $self = shift;
return $self->{path};
}
Ένας accessor ανάγνωσης/εγγραφής:
sub name {
my $self = shift;
$self->{name} = shift if @_;
return $self->{name};
}
Αυτό είναι το επαναλαμβανόμενο boilerplate που το σύγχρονο χαρακτηριστικό class αντικαθιστά με :reader και :writer.
Γιατί αναφορές hash#
Οι αναφορές hash κυριαρχούν επειδή σας επιτρέπουν να προσθέτετε και να μετονομάζετε πεδία χωρίς να αλλάζετε τη διάταξη, και επειδή τα ονομασμένα κλειδιά επιβιώνουν ενός round-trip μέσω Data::Dumper ή JSON::encode. Οι αναφορές πινάκων είναι ταχύτερες και πιο συμπαγείς αλλά εύθραυστες: η αναρίθμηση μιας θυρίδας σπάει κάθε σημείο πρόσβασης.
Τα στιγμιότυπα με υπόβαθρο hash έχουν ένα σοβαρό μειονέκτημα: καμία ενθυλάκωση. Οποιοσδήποτε καλών μπορεί να φτάσει στα εσωτερικά του αντικειμένου με $obj->{path}. Το κλασικό μοντέλο δεν έχει κανένα εργαλείο να τους σταματήσει - η αυτοπειθαρχία και η ανασκόπηση κώδικα είναι οι μόνες άμυνες. Αυτός είναι ο μεγαλύτερος μεμονωμένος λόγος για να προτιμήσετε το σύγχρονο χαρακτηριστικό class για νέο κώδικα.
Κληρονομικότητα με @ISA#
Η κληρονομικότητα είναι μια λίστα σε επίπεδο πακέτου με όνομα @ISA. Η αποστολή μεθόδων τη διασχίζει όταν το τρέχον πακέτο δεν ορίζει τη μέθοδο.
package File::MP3;
our @ISA = ('File');
sub print_info {
my $self = shift;
$self->SUPER::print_info;
print "Title: $self->{title}\n";
}
1;
SUPER:: means «look for this method starting one step up the ISA chain from the package the current method was compiled in»
not from the object’s class. That subtlety matters in diamond inheritance; the inheritance chapter covers it.
Δήλωση του γονέα - τέσσερις τρόποι#
Υπάρχουν τέσσερις γραφές της ίδιας ιδέας. Προτιμήστε την τρίτη για νέο κώδικα που εξακολουθείτε να συντηρείτε με κλασικό ύφος.
# 1. Raw @ISA assignment.
package Child;
our @ISA = ('Parent');
# 2. use base.
package Child;
use base 'Parent'; # deprecated in modern Perl
# 3. use parent.
package Child;
use parent 'Parent'; # current recommendation for classical
# 4. @ISA with a require.
package Child;
require Parent;
our @ISA = ('Parent');
Το use parent χειρίζεται τη φόρτωση του γονικού αρθρώματος και την προσθήκη στο @ISA σε μία γραμμή. Το use base είναι μια παλαιότερη παραλλαγή που εισάγει επιπλέον πεδία τύπου Exporter· αποφύγετέ το.
Ο επικαλούμενος $self#
Κάθε μέθοδος είναι μια υπορουτίνα της οποίας το πρώτο όρισμα είναι είτε το όνομα της κλάσης (για μεθόδους κλάσης όπως η new) είτε το αντικείμενο (για μεθόδους στιγμιότυπου). Η σύμβαση είναι να γίνεται shift σε μεταβλητή με όνομα $self:
sub method {
my $self = shift;
...
}
Ή να γίνεται αποπακετάρισμα μαζί με άλλα ορίσματα:
sub method {
my ($self, $arg1, $arg2) = @_;
...
}
Δεν υπάρχει επιβολή κατά τον χρόνο εκτέλεσης. Αν ξεχάσετε να κάνετε shift το $self, το πρώτο όρισμα που δίνει ο χρήστης γίνεται σιωπηρά $self και επικρατεί χάος. Οι στατικοί αναλυτές το πιάνουν· η γλώσσα όχι.
Καταστροφείς#
Μια μέθοδος DESTROY, αν ορίζεται, εκτελείται όταν ο μετρητής αναφορών του αντικειμένου πέσει στο μηδέν. Χρησιμοποιήστε την για να απελευθερώσετε εξωτερικούς πόρους:
sub DESTROY {
my $self = shift;
close $self->{fh} if defined $self->{fh};
}
Παγίδες που δαγκώνουν κάθε έργο τουλάχιστον μία φορά:
Η
DESTROYεκτελείται σε απρόβλεπτες χρονικές στιγμές. Μη βασίζεστε σε αυτήν για καθαρισμό κρίσιμο για την ορθότητα - μια ρητή$obj->closeνικά την σιωπηρή καταστροφή.Η
DESTROYμπορεί να δει ένα μερικώς κατασκευασμένο αντικείμενο αν ηnewπέθανε στη μέση. Οι αμυντικοί έλεγχοιdefinedείναι φτηνή ασφάλεια.Κατά την καθολική καταστροφή (χρόνος
END), οι εξαρτήσεις μπορεί να έχουν ήδη χαθεί. ΜιαDESTROYπου διασχίζει αναφορές μπορεί να αποτύχει με «Attempt to free unreferenced scalar» ή παρόμοιο. Τυλίξτε τον επικίνδυνο καθαρισμό σεeval { ... }.
AUTOLOAD#
Αν μια κλήση μεθόδου δεν βρει αντίστοιχη sub στην κλάση ή στους προγόνους της, η Perl ψάχνει για μια sub AUTOLOAD. Το όνομα της καλούμενης μεθόδου εμφανίζεται στο our $AUTOLOAD:
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
return if $name eq 'DESTROY'; # critical
...
}
Ο φύλακας DESTROY είναι υποχρεωτικός: χωρίς αυτόν, η καταστροφή του αντικειμένου ενεργοποιεί την AUTOLOAD με $name = 'DESTROY' και είτε συμπεριφέρεστε λάθος είτε καταρρέετε.
Η AUTOLOAD είναι ισχυρή και εύκολο να καταχραστεί. Οι περιπτώσεις όπου αξίζει τη θέση της:
Οκνηρή δημιουργία accessor - συνθέστε έναν accessor στην πρώτη κλήση και μετά εγκαταστήστε τον ως πραγματική sub μέσω ανάθεσης typeglob, ώστε η επόμενη κλήση να είναι άμεση.
Προώθηση - αναθέστε άγνωστες μεθόδους σε ένα εμπεριεχόμενο αντικείμενο (αλλά δείτε Ρόλοι και ανάθεση για μια καθαρότερη προσέγγιση).
Περιπτώσεις όπου βλάπτει περισσότερο από όσο βοηθάει: να προσποιείται ότι είναι ένα πλήρες MOP, να πιάνει τυπογραφικά ως σιωπηρά no-op, να υλοποιεί «κλάσεις» των οποίων όλη η επιφάνεια είναι η AUTOLOAD.
Ένα επεξεργασμένο παράδειγμα#
Μια κλασική κλάση Counter με accessor ανάγνωσης/εγγραφής και κληρονομικότητα:
package Counter;
sub new {
my ($class, %args) = @_;
my $self = {
value => $args{value} // 0,
step => $args{step} // 1,
};
return bless $self, $class;
}
sub value {
my $self = shift;
$self->{value} = shift if @_;
return $self->{value};
}
sub step { $_[0]{step} }
sub tick {
my $self = shift;
$self->{value} += $self->{step};
}
1;
Μια υποκλάση που διπλασιάζει το βήμα:
package Counter::Double;
use parent 'Counter';
sub new {
my ($class, %args) = @_;
$args{step} //= 2;
return $class->SUPER::new(%args);
}
1;
Σε σύγκριση με την έκδοση σύγχρονης κλάσης, η κλασική μορφή έχει περίπου τρεις φορές τις γραμμές, και καθεμιά τους είναι ένα σημείο όπου μπορείτε να κάνετε λάθος.
Πότε η κλασική αντικειμενοστρέφεια είναι η σωστή επιλογή#
Παρ” όλα αυτά, υπάρχουν θεμιτοί λόγοι να μείνετε με κλάσεις βασισμένες σε bless:
Συντηρείτε υπάρχοντα κώδικα. Η επανεγγραφή μιας κλασικής κλάσης που λειτουργεί σε μορφή
classείναι έργο μετάβασης, όχι δωρεάν γεύμα - δείτε το κεφάλαιο μετάβασης.Πρέπει να υποστηρίξετε παλαιότερη Perl. Αν ο κώδικάς σας πρέπει να τρέχει σε Perl πριν την 5.38, το χαρακτηριστικό
classδεν είναι διαθέσιμο.Χρειάζεστε αντικείμενα με υπόβαθρο πίνακα ή κώδικα. Το
classδημιουργεί μόνο στιγμιότυπα με υπόβαθρο hash. Αν χτίζετε μια κρίσιμη για την απόδοση δομή με στιγμιότυπα αναφοράς πίνακα, το κλασικόblessπαραμένει η απάντηση.Κάνετε κάτι που το χαρακτηριστικό
classσκόπιμα απαγορεύει - ξανανοίγετε το πακέτο από έξω, πολλαπλή κληρονομικότητα ή βαθύ μεταπρογραμματισμό μέσω χειρισμού του@ISA.
Μια ιστορική σημείωση για τα συστήματα αντικειμενοστρέφειας του CPAN#
Επειδή το κλασικό μοντέλο αφήνει τόσα πολλά στη σύμβαση, το CPAN ανέπτυξε ένα οικοσύστημα αρθρωμάτων που προσθέτουν δηλωτική σύνταξη από πάνω:
Moose - πλήρες σύστημα αντικειμενοστρέφειας με μινιατούρα τύπων, ρόλους, method modifiers, πλήρες API ενδοσκόπησης και ένα μεγάλο οικοσύστημα επεκτάσεων. Βαρύ κατά τη φόρτωση· η κυρίαρχη επιλογή για μεγάλες εφαρμογές που χτίστηκαν μεταξύ 2007 και περίπου 2020.
Moo - υποσύνολο του API του Moose χωρίς το στρώμα ενδοσκόπησης. Ελαφρύτερο, pure-Perl, αρκετά συμβατό σε επίπεδο API ώστε μια κλάση Moo και μια κλάση Moose να μοιράζονται ένα δέντρο κληρονομικότητας.
Class::Accessor - δημιουργεί απλούς accessors και τη
new. Μινιμαλιστικό, pure-Perl, χωρίς ρόλους.Class::Tiny - το μικρότερο από τα αρθρώματα δημιουργίας accessor. Όλοι οι accessors είναι ανάγνωσης/εγγραφής, χωρίς ελέγχους τύπου.
Object::Pad - το πειραματικό πρωτότυπο που έγινε το χαρακτηριστικό
classτου πυρήνα. Παραμένει εγκαταστάσιμο από το CPAN για κώδικα που χρειάζεται το χαρακτηριστικό σε παλαιότερες Perl.Role::Tiny - σύνθεση ρόλων για έργα που δεν χρησιμοποιούν Moose αλλά θέλουν παρ” όλα αυτά κοινή χρήση τύπου
does.
Αυτά τα συστήματα αποτελούν ιστορικό πλαίσιο στην pperl. Ο νέος κώδικας στην pperl θα πρέπει να χρησιμοποιεί το χαρακτηριστικό class του πυρήνα, που καλύπτει το έδαφος που πρωτοπόρησαν τα Moose/Moo χωρίς το κόστος φόρτωσης και εξαρτήσεων. Όταν κληρονομείτε μια βάση κώδικα που χρησιμοποιεί κάποιο από αυτά, μάθετε όσα μόλις χρειάζονται από την επιφάνειά του ώστε να παραμείνετε παραγωγικοί και προγραμματίστε τη μετάβαση με το κεφάλαιο μετάβασης.