מחלקות מודרניות#
התכונה class היא הדרך המומלצת לכתוב קוד מונחה־עצמים ב־pperl. היא מחליפה את הבנאי שנכתב ביד, את ייצור ה־accessors הידני, את ניהול חריצי האש, ואת ריקוד use parent בתחביר הצהרתי יחיד. אתם מתארים מה המחלקה היא; זמן הריצה מסדר איך היא ממומשת.
הפרק הזה עובר על התכונה כפי שמתכנת פועל יפגוש אותה: תחילה מחלקה מינימלית, אז שדות ותכונות, אז מתודות, אז ווים בזמן הבנייה, ואז ירושה. כל פרק קצר במכוון; כאשר אתם רוצים את כל פני השטח של מילת מפתח, עקבו אחר הקישור הצולב לדף העיון שלה.
הפעלת התכונה#
התכונה מסומנת ניסיונית ב־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. זמן הריצה מייצר את הבנאי מתוך הצהרות ה־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 }
}
שדות מקבלים סקלרים, מערכים, והאשים:
class Registry {
field $name;
field @items;
field %seen;
}
תכונות על שדות#
תכונות שדה מסירות מכם את העבודה ההצהרתית.
: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;
}
}
מתודות משתמשות בחתימות אוטומטית - use feature 'signatures' מובן מאליו בתוך גוף מחלקה. $self הוא הארגומנט הראשון המשתמע של המתודה ואינו מופיע בחתימה.
מתודות פרטיות#
מתודה שמוצהרת עם my היא לקסיקלית לגוף המחלקה ומופעלת דרך האופרטור ->&:
class Safe {
field $secret :param;
my method check ($n) { $n == $secret }
method unlock ($n) {
return $self->&check($n) ? 'open' : 'denied';
}
}
my method הוא הקרוב ביותר שיש ל־pperl למתודה פרטית קשה; שום קורא חיצוני לא יכול להגיע אליה כי השם אינו חי בחבילה כלל.
ווי בנייה: ADJUST#
בלוק ADJUST רץ לאחר אתחול השדות, כאשר $self כבר קשור. זה המקום עבור:
תיקוף לאחר־בנייה.
שדות נגזרים התלויים בשדות אחרים.
פתיחת מטפל או התחברות למשאב שאורך החיים שלו תואם את העצם.
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 של ההורה רצים לפני אלה של הילד.
ירושה#
מחלקה יורשת מהורה אחד לכל היותר באמצעות התכונה :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) הוא שגיאת תחביר, ומחלקה לא יכולה לשאת יותר מתכונת :isa אחת. פרק הירושה מסביר מדוע, ומכסה את החלופה מבוססת התפקידים.
קריאה במעלה השרשרת#
SUPER:: עדיין עובד, בדיוק כפי שהוא עובד עבור מחלקות מבוססות bless:
class Dog :isa(Animal) {
method introduce {
$self->SUPER::introduce;
say "and I can fetch";
}
}
__CLASS__#
בתוך מאתחל של שדה או גוף מתודה, __CLASS__ הוא המחלקה הנבנית כעת. בתת־מחלקה, __CLASS__ מתייחס לתת־המחלקה גם בתוך מתודה נירשת או מאתחל שדה נירש. השתמשו בו לווים בסגנון מפעל:
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
שיתוף פעולה עם OO קלאסי#
מחלקה שמוצהרת עם 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}) - ראו את פרק ההעברה לסיפור ה־interop המלא.
מקרי קצה שכדאי להכיר#
אל תכתבו
sub new. הבנאי מיוצר.newשנכתב על־ידי המשתמש בתוך גוףclassנדחה בזמן הידור.התייחסו אל
@ISAכאל לקריאה בלבד עבור מחלקות שמוצהרות עםclass. מניפולציה בזמן ריצה היא התנהגות לא־מוגדרת.$selfהוא לקריאה בלבד בתוך מתודה. השמה ל־$selfהיא שגיאת זמן ריצה.packageאינו יכול לפתוח מחדש מחלקה. ברגע שמרחב שמות הוצהר עםclass, הוראתpackageמאוחרת יותר לאותו שם היא שגיאת זמן־הידור, ולהיפך.היקף של צורת־הוראה.
class Foo;צורך את שאר הבלוק העוטף, בדרך כלל את שאר הקובץ.classעוקב אוpackageמסיים את הגוף; אין דרך לסגור מחלקה בצורת־הוראה מוקדם.
לאן ממשיכים מכאן#
ירושה ופתרון מתודות - הסיפור המלא של איך פועל חיפוש מתודה ברחבי שרשראות
:isaוברחבי היררכיות מעורבות קלאסיות/מודרניות.תפקידים והאצלה - התשובה המומלצת ל״אני רוצה לשתף התנהגות בין מחלקות לא קשורות.״
העברה מהקלאסי למודרני - אם אתם בוהים במחלקה מבוססת
blessותוהים איך נראה בלוק ה־classהשקול.