Σύγχρονες κλάσεις#

Το χαρακτηριστικό class είναι ο συνιστώμενος τρόπος συγγραφής αντικειμενοστραφούς κώδικα στην pperl. Αντικαθιστά τον χειροποίητο κατασκευαστή, τη χειροκίνητη δημιουργία accessor, τη λογιστική των hash-slot και τον χορό του use parent με μία ενιαία δηλωτική σύνταξη. Εσείς περιγράφετε τι είναι η κλάση· το runtime οργανώνει πώς υλοποιείται.

Αυτό το κεφάλαιο διασχίζει το χαρακτηριστικό όπως θα το συναντούσε ένας προγραμματιστής εν δράσει: πρώτα μια ελάχιστη κλάση, μετά πεδία και attributes, μετά μεθόδους, μετά hooks κατασκευής και τέλος κληρονομικότητα. Κάθε ενότητα είναι σύντομη σκόπιμα· όταν θέλετε την πλήρη επιφάνεια μιας δεσμευμένης λέξης, ακολουθήστε τη διασταυρωμένη σύνδεση προς τη σελίδα αναφοράς της.

Ενεργοποίηση του χαρακτηριστικού#

Το χαρακτηριστικό σημειώνεται ως πειραματικό στην Perl 5.42 και η pperl κατοπτρίζει αυτή την κατάσταση. Κάθε αρχείο που το χρησιμοποιεί ανοίγει με:

use v5.38;
use feature 'class';
no warnings 'experimental::class';

Η γραμμή use v5.38 από μόνη της ενεργοποιεί ήδη το χαρακτηριστικό class, αλλά η ρητή δήλωση κάνει την εξάρτηση ορατή σε όποιον διαβάζει το αρχείο.

Μια ελάχιστη κλάση#

use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Point {
    field $x :param;
    field $y :param;

    method distance_from_origin {
        return sqrt($x ** 2 + $y ** 2);
    }
}

my $p = Point->new(x => 3, y => 4);
say $p->distance_from_origin;                # 5

Τρία πράγματα που πρέπει να προσέξετε:

  • Δεν υπάρχει sub new. Το runtime δημιουργεί τον κατασκευαστή από τις δηλώσεις field. Ένα sub new γραμμένο από τον χρήστη μέσα σε σώμα class είναι σφάλμα μεταγλώττισης σκόπιμα - ο δημιουργημένος γνωρίζει πώς να συνδέσει τα :param, να εκτελέσει αρχικοποιητές και να ενεργοποιήσει τα μπλοκ ADJUST με τη σωστή σειρά.

  • Τα πεδία αναφέρονται με το δηλωμένο όνομά τους μέσα στις μεθόδους, όχι μέσω του $self->{x}. Η αποθήκευση του στιγμιότυπου είναι γνήσια ιδιωτική· κανένας κώδικας έξω από το σώμα της κλάσης δεν μπορεί να την πειράξει.

  • Η δεσμευμένη λέξη method φροντίζει για το $self. Δεν γράφετε ποτέ my $self = shift.

Πεδία#

Ένα field είναι αποθήκευση ανά στιγμιότυπο. Κάθε αντικείμενο της κλάσης παίρνει τη δική του θυρίδα· κάθε μέθοδος και κάθε μπλοκ ADJUST βλέπει το πεδίο σαν να ήταν λεξιλογική μεταβλητή.

class Counter {
    field $value = 0;
    field $step  = 1;

    method tick  { $value += $step }
    method value { $value }
}

Τα πεδία δέχονται βαθμωτά, πίνακες και hashes:

class Registry {
    field $name;
    field @items;
    field %seen;
}

Attributes σε πεδία#

Τα attributes των πεδίων αφαιρούν τη δηλωτική δουλειά από τα χέρια σας.

  • :param - συνδέει αυτό το πεδίο με ένα όρισμα του κατασκευαστή. Αν ο καλών το παραλείψει, το πεδίο είτε μένει χωρίς τιμή είτε παίρνει τη δηλωμένη προεπιλογή.

  • :param(alt) - συνδέει με διαφορετικό όνομα στον κατασκευαστή.

  • :reader - δημιουργεί έναν accessor μόνο για ανάγνωση με το όνομα του πεδίου χωρίς το sigil.

  • :reader(custom) - δημιουργεί έναν reader με διαφορετικό όνομα.

  • :writer - δημιουργεί έναν setter (set_FIELDNAME).

class User {
    field $name  :param :reader;
    field $email :param :reader :writer;
    field $role  :param :reader = 'guest';
}

my $u = User->new(name => 'Ada', email => 'ada@example.org');
say $u->name;                                 # Ada
$u->set_email('ada@analytical.org');
say $u->role;                                 # guest

Προεπιλεγμένες τιμές#

Οι προεπιλογές χρησιμοποιούν έναν από τρεις τελεστές, καθένας με διαφορετικό κανόνα:

  • = - εφαρμόζεται όταν ο καλών παρέλειψε εντελώς την παράμετρο.

  • //= - εφαρμόζεται επίσης όταν ο καλών έδωσε undef.

  • ||= - εφαρμόζεται επίσης όταν ο καλών έδωσε οποιαδήποτε ψευδή τιμή.

class Window {
    field $title :param         = 'untitled';
    field $width :param         //= 640;
    field $shown :param         ||= 1;
}

Επιλέξτε = εκτός αν έχετε συγκεκριμένο λόγο να επιτρέψετε τις ισχυρότερες μορφές. Η σιωπηρή προαγωγή του undef ή του 0 σε προεπιλογή είναι μαγνήτης σφαλμάτων.

Τα πεδία δεν είναι λεξιλογικές μεταβλητές με τη συνήθη έννοια#

Τα πεδία μοιάζουν με λεξιλογικές μεταβλητές μέσα στο σώμα μιας μεθόδου, αλλά έχουν αυστηρότερους κανόνες:

  • Δεν μπορείτε να κάνετε our ένα πεδίο, δεν μπορείτε να κάνετε local ένα πεδίο και δεν μπορείτε να πάρετε αναφορά σε ένα πεδίο και να την εξάγετε.

  • Τα πεδία είναι ιδιωτικά στην κλάση. Οι υποκλάσεις δεν κληρονομούν τα πεδία ενός γονέα· αν μια υποκλάση χρειάζεται την κατάσταση του γονέα, τη ζητάει μέσω μεθόδου.

  • Ο αρχικοποιητής ενός πεδίου εκτελείται πριν συνδεθεί το $self. Μέσα σε field $x = EXPR, μπορείτε να χρησιμοποιήσετε προγενέστερα πεδία με το όνομά τους και μπορείτε να χρησιμοποιήσετε το __CLASS__, αλλά δεν μπορείτε να χρησιμοποιήσετε το $self. Οτιδήποτε χρειάζεται το πλήρως κατασκευασμένο στιγμιότυπο ανήκει σε ένα μπλοκ ADJUST.

Μέθοδοι#

Μια method είναι μια υπορουτίνα που συνδέει αυτόματα το $self και διαβάζει τα πεδία της περικλείουσας κλάσης με το όνομά τους.

class File {
    field $path :param :reader;

    method slurp {
        open my $fh, '<', $path or die "open $path: $!";
        local $/;
        return <$fh>;
    }

    method rename ($new) {
        rename $path, $new or die "rename: $!";
        $path = $new;
    }
}

Οι μέθοδοι χρησιμοποιούν αυτόματα signatures - το use feature 'signatures' υπονοείται μέσα σε σώμα κλάσης. Το $self είναι το σιωπηρό πρώτο όρισμα της μεθόδου και δεν εμφανίζεται στο signature.

Ιδιωτικές μέθοδοι#

Μια μέθοδος που δηλώνεται με my είναι λεξιλογική στο σώμα της κλάσης και καλείται μέσω του τελεστή ->&:

class Safe {
    field $secret :param;

    my method check ($n) { $n == $secret }

    method unlock ($n) {
        return $self->&check($n) ? 'open' : 'denied';
    }
}

Η my method είναι ό,τι πιο κοντινό έχει η pperl σε αυστηρά ιδιωτική μέθοδο· κανείς εξωτερικός καλών δεν μπορεί να την προσεγγίσει επειδή το όνομα δεν υπάρχει καθόλου στο πακέτο.

Hooks κατασκευής: ADJUST#

Ένα μπλοκ ADJUST εκτελείται μετά την αρχικοποίηση των πεδίων, με το $self ήδη συνδεδεμένο. Είναι το κατάλληλο σημείο για:

  • Επικύρωση μετά την κατασκευή.

  • Παράγωγα πεδία που εξαρτώνται από άλλα πεδία.

  • Άνοιγμα ενός handle ή σύνδεση σε έναν πόρο του οποίου η διάρκεια ζωής ταυτίζεται με αυτή του αντικειμένου.

class TempFile {
    field $prefix :param = 'tmp';
    field $path;
    field $fh;

    ADJUST {
        $path = "/tmp/$prefix-$$-" . int(rand 1_000_000);
        open $fh, '>', $path or die "open $path: $!";
    }

    method write ($data) { print {$fh} $data }
    method path { $path }
}

Πολλαπλά μπλοκ ADJUST σε μία κλάση εκτελούνται με τη σειρά δήλωσης. Σε αλυσίδα κληρονομικότητας, τα μπλοκ ADJUST του γονέα εκτελούνται πριν εκείνα του παιδιού.

Κληρονομικότητα#

Μια κλάση κληρονομεί από το πολύ έναν γονέα χρησιμοποιώντας το attribute :isa:

class Shape {
    field $colour :param :reader = 'black';
    method area { 0 }
}

class Circle :isa(Shape) {
    field $radius :param :reader;
    method area { 3.14159265 * $radius ** 2 }
}

Κληρονομούνται μόνο οι μέθοδοι. Τα πεδία είναι αποθήκευση ανά κλάση. Μια υποκλάση που χρειάζεται την κατάσταση του γονέα τη ζητάει μέσω ενός reader:

class Labelled :isa(Shape) {
    field $label :param :reader;
    method describe { "$label (" . $self->colour . ')' }
}

Η πολλαπλή κληρονομικότητα δεν υποστηρίζεται. Το :isa(A, B) είναι συντακτικό σφάλμα, και μια κλάση δεν μπορεί να φέρει περισσότερα από ένα attribute :isa. Το κεφάλαιο για την κληρονομικότητα εξηγεί γιατί και καλύπτει την εναλλακτική που βασίζεται σε ρόλους.

Κλήση προς τα πάνω στην αλυσίδα#

Το SUPER:: συνεχίζει να λειτουργεί, ακριβώς όπως σε κλάσεις βασισμένες σε bless:

class Dog :isa(Animal) {
    method introduce {
        $self->SUPER::introduce;
        say "and I can fetch";
    }
}

__CLASS__#

Μέσα σε αρχικοποιητή πεδίου ή σε σώμα μεθόδου, το __CLASS__ είναι η κλάση που κατασκευάζεται εκείνη τη στιγμή. Σε υποκλάση, το __CLASS__ αναφέρεται στην υποκλάση ακόμη και μέσα σε κληρονομημένη μέθοδο ή σε κληρονομημένο αρχικοποιητή πεδίου. Χρησιμοποιήστε το για hooks τύπου factory:

class Base {
    sub DEFAULT_X { 10 }
    field $x = __CLASS__->DEFAULT_X;
    method x { $x }
}

class Tuned :isa(Base) {
    sub DEFAULT_X { 99 }
}

say Tuned->new->x;                            # 99

Διαλειτουργικότητα με την κλασική αντικειμενοστρέφεια#

Μια κλάση δηλωμένη με class είναι ένα κανονικό πακέτο. Κώδικας που χρησιμοποιεί ref, τον τελεστή isa, UNIVERSAL::isa και UNIVERSAL::can συνεχίζει να λειτουργεί:

my $c = Circle->new(radius => 2);
say ref $c;                                   # Circle
say $c isa Shape ? 'yes' : 'no';              # yes
say $c->can('area') ? 'yes' : 'no';           # yes

Μια κλασική κλάση μπορεί να γίνει υποκλάση μιας σύγχρονης κλάσης και αντίστροφα, αρκεί η κλασική πλευρά να περνά μέσα από μεθόδους (ποτέ μέσω $self->{fieldname}) - δείτε το κεφάλαιο μετάβασης για την πλήρη ιστορία διαλειτουργικότητας.

Οριακές περιπτώσεις που αξίζει να γνωρίζετε#

  • Μη γράφετε sub new. Ο κατασκευαστής δημιουργείται αυτόματα. Ένα new γραμμένο από τον χρήστη μέσα σε σώμα class απορρίπτεται κατά τη μεταγλώττιση.

  • Αντιμετωπίστε το @ISA ως μόνο για ανάγνωση για κλάσεις δηλωμένες με class. Η τροποποίηση κατά τον χρόνο εκτέλεσης είναι απροσδιόριστη συμπεριφορά.

  • Το $self είναι μόνο για ανάγνωση μέσα σε μέθοδο. Η ανάθεση στο $self είναι σφάλμα χρόνου εκτέλεσης.

  • Το package δεν μπορεί να ξανανοίξει μια κλάση. Μόλις ένας χώρος ονομάτων δηλωθεί με class, μια μεταγενέστερη δήλωση package για το ίδιο όνομα είναι σφάλμα μεταγλώττισης, και αντίστροφα.

  • Εμβέλεια μορφής εντολής. Το class Foo; καταναλώνει το υπόλοιπο του περικλείοντος μπλοκ, συνήθως το υπόλοιπο του αρχείου. Μια επόμενη class ή package τερματίζει το σώμα· δεν υπάρχει τρόπος να κλείσετε πρόωρα μια κλάση σε μορφή εντολής.

Πού να πάτε στη συνέχεια#