Μετάβαση από κλασική σε σύγχρονη#
Αν συντηρείτε μια βάση κώδικα βασισμένη σε bless και αναρωτιέστε πώς θα έμοιαζε το ισοδύναμο μπλοκ class, αυτό το κεφάλαιο είναι η συνταγή. Η μετάφραση είναι ως επί το πλείστον μηχανική, αλλά τα σημεία όπου δεν είναι μηχανική είναι εκεί όπου αναδύονται πραγματικές αποφάσεις σχεδίασης - και όπου η μετάβαση αποπληρώνεται μόνη της.
Πριν ξεκινήσετε, διαβάστε το κεφάλαιο της σύγχρονης κλάσης για τη σύνταξη προς την οποία μεταφράζετε, και διατρέξτε το κεφάλαιο της κλασικής αντικειμενοστρέφειας για όποιες κατασκευές χρησιμοποιεί ο υπάρχων κώδικας και ίσως δεν θυμάστε με λεπτομέρεια.
Πότε να μεταβείτε#
Η μετάβαση δεν είναι δωρεάν. Κάντε την όταν αποκομίζετε πραγματική αξία:
Η κλάση βρίσκεται υπό ενεργή ανάπτυξη. Πρόκειται να προσθέσετε πεδία, να προσθέσετε μεθόδους ή να αλλάξετε το συμβόλαιο του κατασκευαστή. Το να γίνεται αυτή η δουλειά πάνω στο χαρακτηριστικό
classείναι λιγότερο επιρρεπές σε σφάλματα από το να γίνεται πάνω σε χειροποίητες hash-slot.Η κλάση έχει ασαφή ενθυλάκωση. Οι καλούντες φτάνουν απευθείας στο
$obj->{fieldname}, και θέλετε να το σταματήσετε χωρίς να ξαναγράψετε κάθε σημείο κλήσης ταυτόχρονα. Το χαρακτηριστικόclassκάνει την παραβίαση σφάλμα μεταγλώττισης τη στιγμή που μεταβαίνετε.Η κλάση κάθεται πάνω σε Moose ή Moo, και πληρώνετε το κόστος φόρτωσης χωρίς να χρησιμοποιείτε τα χαρακτηριστικά με βαριές εξαρτήσεις. Το σύγχρονο
classαποτελεί άμεση αντικατάσταση για τις περισσότερες κλάσεις Moose/Moo που χρησιμοποιούν μόνοhasκαιwith.
Μη μεταφέρετε μια κλάση που λειτουργεί και είναι παγωμένη. Μια σταθερή κλασική κλάση που δεν πρόκειται να δει νέο κώδικα για άλλα πέντε χρόνια είναι ακριβώς το ίδιο καλή με το σύγχρονο αντίστοιχό της, και η ίδια η μετάβαση θα εισαγάγει σφάλματα που η σταθερή κλάση δεν έχει.
Η μηχανική μετάφραση#
Πάρτε αυτή την κλασική κλάση:
package Counter;
sub new {
my ($class, %args) = @_;
my $self = {
value => $args{value} // 0,
step => $args{step} // 1,
};
die "step must be positive" if $self->{step} <= 0;
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;
Η σύγχρονη μορφή:
use v5.38;
use feature 'class';
no warnings 'experimental::class';
class Counter {
field $value :param :reader :writer = 0;
field $step :param :reader = 1;
ADJUST {
die "step must be positive" if $step <= 0;
}
method tick { $value += $step }
}
1;
Η αντιστοίχιση, κομμάτι κομμάτι:
Κλασική κατασκευή | Σύγχρονη κατασκευή |
|---|---|
|
|
| (διαγραμμένο - δημιουργείται αυτόματα) |
Προεπιλογή ορίσματος κατασκευαστή με |
|
|
|
Accessor μόνο για ανάγνωση |
|
Accessor ανάγνωσης/εγγραφής |
|
Επικύρωση στο τέλος της | Μπλοκ |
|
|
| (διαγραμμένο - σιωπηρό) |
|
|
| αμετάβλητο - εξακολουθεί να λειτουργεί |
Τα μη μηχανικά μέρη#
Όλα τα παραπάνω είναι μετάφραση από μνήμης. Τα σημεία όπου χρειάζεται να σκεφτείτε είναι όλα παραλλαγές του «ο κλασικός κώδικας έκανε κάτι που το χαρακτηριστικό class δεν κάνει με τον ίδιο τρόπο».
Πολλαπλή κληρονομικότητα#
Το class δεν υποστηρίζει :isa(A, B). Αν η κλασική κλάση έχει @ISA = ('Parent1', 'Parent2'), έχετε τρεις επιλογές:
Συμπτύξτε σε απλή κληρονομικότητα. Συνήθως ο δεύτερος γονέας είναι ένα mixin ή ρόλος μεταμφιεσμένος. Μεταφέρετέ τον σε έναν ρόλο και καταναλώστε τον με
with.Συμπτύξτε σε σύνθεση. Αν ο δεύτερος γονέας είναι συνεργάτης παρά πρόγονος, μεταβείτε σε ανάθεση αντί για κληρονομικότητα.
Μη μεταφέρετε την κλάση. Αν η πολλαπλή κληρονομικότητα είναι γνήσια κρίσιμη και καμία από τις παραπάνω δεν ταιριάζει, η κλάση δεν ταιριάζει στο σύγχρονο χαρακτηριστικό. Αφήστε τη κλασική μέχρι να φτάσει το χαρακτηριστικό ρόλων του πυρήνα.
Στην πράξη η επιλογή 1 ή 2 ισχύει για περίπου το 95% των περιπτώσεων πολλαπλής κληρονομικότητας σε πραγματικό κώδικα.
Άμεση πρόσβαση hash από τους καλούντες#
Αν εξωτερικοί καλούντες κάνουν $counter->{value} αντί για $counter->value, η μετάβαση της κλάσης θα τους σπάσει. Το σφάλμα του μεταγλωττιστή είναι καθαρό και τοπικό - το μήνυμα σφάλματος κατονομάζει το σημείο - αλλά η μετάβαση δεν είναι πλέον απευθείας αντικαταστάτης.
Στρατηγική άμβλυνσης:
Έλεγχος πρώτα. Κάντε grep για πρότυπα
$obj_name->{σε όλη τη βάση κώδικα. Ο αριθμός είναι συνήθως μικρός.Προσθέστε έναν accessor στην κλασική κλάση που να καλύπτει κάθε πεδίο στο οποίο γίνεται πρόσβαση.
Αντικαταστήστε τα σημεία κλήσης
$obj->{fieldname}με$obj->fieldname()σε ένα αφιερωμένο commit.Μετά μεταφέρετε την κλάση στη σύγχρονη μορφή.
Ο διαχωρισμός της αναδιαμόρφωσης σε δύο φάσεις κρατάει κάθε commit μικρό και bisectable.
AUTOLOAD#
Η AUTOLOAD σε κλασική κλάση δεν έχει σύγχρονο ισοδύναμο. Το χαρακτηριστικό class δεν σας επιτρέπει να πιάνετε κλήσεις απουσιαζουσών μεθόδων.
Αν η AUTOLOAD κάνει ένα από τα κοινά πρότυπα, μεταφράστε το:
Οκνηρή σύνθεση accessor - δηλώστε τους accessors ρητά ως πεδία με
:readerή:writer. Η δημιουργία κώδικα του χαρακτηριστικούclassαντικαθιστά ό,τι έκανε ηAUTOLOAD.Ανάθεση σε εμπεριεχόμενο αντικείμενο - αντικαταστήστε με ρητές μεθόδους προώθησης (δείτε ρόλοι και ανάθεση) ή με έναν accessor
handlesτύπου Moose αν η βάση κώδικα μπορεί να εξαρτηθεί από το Moose.Ένα γενικό proxy - κρατήστε την κλάση κλασική. Το χαρακτηριστικό
classδεν είναι το σωστό εργαλείο.
DESTROY#
Το class δεν εισάγει μια μορφή καταστροφέα πρώτης τάξης. Αν η κλασική κλάση έχει DESTROY, γράψτε την ως απλή μέθοδο μέσα στο μπλοκ class - καλείται μέσω του ίδιου hook μετρητή αναφορών:
class Logger {
field $fh;
ADJUST { ... }
sub DESTROY {
my $self = shift;
close $self->{fh} if $self->{fh};
}
}
Προσέξτε τον σπυρί: μέσα στη DESTROY δεν μπορείτε να ονομάσετε το πεδίο με το δηλωμένο όνομά του, επειδή τα σώματα sub δεν βλέπουν τις συνδέσεις πεδίων. Είτε χρησιμοποιήστε μια κανονική μέθοδο στην οποία αναθέτει η DESTROY, είτε αποδεχτείτε την ελαφρά ασυμμετρία.
Ένα καθαρότερο πρότυπο όταν η διάρκεια ζωής είναι υπό τον έλεγχό σας είναι να προσθέσετε μια ρητή μέθοδο close και να την καλείτε ντετερμινιστικά, αφήνοντας τη DESTROY ως ζώνη-και-τιράντες εφεδρεία.
Στιγμιότυπα που δεν είναι hash#
Αν η κλασική κλάση κάνει bless \@self, $class ή bless \$self, $class - στιγμιότυπο με υπόβαθρο πίνακα ή βαθμωτού - το σύγχρονο χαρακτηριστικό δεν έχει ισοδύναμο. Η διάταξη στιγμιότυπου τύπου hash είναι ψημένη μέσα. Κρατήστε αυτές τις κλάσεις κλασικές.
Tied hashes / overloading#
Οι ενσωματώσεις use overload και tie λειτουργούν αμετάβλητες σε μπλοκ class. Τίποτα ιδιαίτερο να κάνετε.
Μετάβαση κλάσεων Moose / Moo#
Η μετάφραση από Moose σε class είναι καθαρότερη από εκείνη από κλασικό κώδικα επειδή το Moose είναι ήδη δηλωτικό. Ο αδρός χάρτης:
Moose / Moo | Σύγχρονο |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
| (αναμένετε τους ρόλους του πυρήνα) |
| (κανένα άμεσο ισοδύναμο) |
| (κανένα άμεσο ισοδύναμο) |
Τα στοιχεία που λείπουν - περιορισμοί τύπων, method modifiers, ρόλοι - είναι αυτά που κρατούν μερικές βάσεις κώδικα Moose στο Moose προς το παρόν. Αν η χρήση του Moose περιορίζεται σε has, extends και BUILD, η μετάβαση είναι άμεση. Αν εξαρτάται έντονα από το σύστημα τύπων του Moose ή από τους method modifiers, η μετάβαση είναι έργο, όχι commit.
Δοκιμή της μετάβασης#
Μια μετάβαση κλάσης που δεν αλλάζει εξωτερική συμπεριφορά θα έπρεπε να περάσει την υπάρχουσα σουίτα δοκιμών αμετάβλητη. Βήματα:
Βεβαιωθείτε ότι η κλάση έχει δοκιμές που καλύπτουν το δημόσιο API της. Αν δεν έχει, γράψτε τες πριν τη μετάβαση - οι δοκιμές είναι το δίχτυ ασφαλείας της μετάβασης.
Μεταφέρετε την κλάση.
Τρέξτε τις δοκιμές. Ένα καθαρό πέρασμα είναι το σήμα ολοκλήρωσης.
Κάντε grep στη βάση κώδικα για προσβάσεις
$obj->{...}στα στιγμιότυπα της μεταφερμένης κλάσης. Διορθώστε όσα βρείτε.Αφαιρέστε όποιο πλέον νεκρό
use parent,use base, ρητόsub newή boilerplate accessor χρειαζόταν η παλιά μορφή.
Αν το βήμα 3 αποτύχει, οι αποτυχίες συνήθως εμπίπτουν σε μια σύντομη λίστα:
Άμεση πρόσβαση hash σε μέθοδο που η παλιά μορφή ανεχόταν και η νέα μορφή απορρίπτει. Αντικαταστήστε με αναφορές πεδίων.
Ένα όρισμα κατασκευαστή που γινόταν σιωπηρά αποδεκτό. Ο δημιουργημένος κατασκευαστής είναι αυστηρός με τα ονόματα
:param. Είτε δηλώστε την παράμετρο είτε αλλάξτε το σημείο κλήσης.Ένας αρχικοποιητής πεδίου που αναφερόταν στο
$self. Οι αρχικοποιητές πεδίων εκτελούνται πριν συνδεθεί το$self. Μετακινήστε την εξαρτώμενη λογική σε ένα μπλοκADJUST.
Τι κερδίζετε από αυτό#
Η μετάβαση από κλασική σε σύγχρονη είναι ασυνήθιστη στο ότι το αποτέλεσμα είναι ταυτόχρονα συντομότερο και πιο σωστό:
Συντομότερο. Το παράδειγμα
Counterπαραπάνω πέφτει από ~20 γραμμές σε ~10, και οι γραμμές που αφαιρέθηκαν είναι όλες boilerplate.Ιδιωτικά πεδία. Ο εξωτερικός κώδικας δεν μπορεί πια να φτάσει μέσα. Η ενθυλάκωση που υπερασπιζόσασταν στην ανασκόπηση κώδικα τώρα την υπερασπίζεται ο μεταγλωττιστής.
Κανένα ξεχασμένο
bless. Ο δημιουργημένος κατασκευαστής είναι πάντα ασφαλής ως προς την κληρονομικότητα· δεν μπορείτε να στείλετε έναν κατασκευαστή που κατά λάθος κάνει bless σε λάθος κλάση.Κανένα ξεχασμένο
$self = shift. Η σιωπηρή σύνδεση δεν μπορεί να λείψει.Μια ονομασμένη σημαία χαρακτηριστικού. Το
no warnings 'experimental::class'τεκμηριώνει στην κορυφή του αρχείου από τι εξαρτάται η κλάση.
Περαιτέρω ανάγνωση#
Σύγχρονες κλάσεις - αναφορά για τη σύνταξη προς την οποία μεταβαίνετε.
Κλασική αντικειμενοστρέφεια - αναφορά για τη σύνταξη από την οποία μεταβαίνετε.
Κληρονομικότητα και επίλυση μεθόδων - το κεφάλαιο για τις μεικτές ιεραρχίες, χρήσιμο όσο η βάση κώδικα είναι μισομεταφερμένη.
Ρόλοι και ανάθεση - όπου καταλήγει η πολλαπλή κληρονομικότητα στον μεταφερμένο κώδικα.
class,field,method- οι σελίδες αναφοράς για τις τρεις δεσμευμένες λέξεις στην καρδιά της μετάβασης.bless,ref,isa- οι πρωτογενείς οντότητες που στηρίζουν τόσο την παλιά όσο και τη νέα μορφή και που εξακολουθούν να λειτουργούν σε μεικτές ιεραρχίες.