תיחום · מחלקות ותכנות מונחה־עצמים

class#

מצהיר על מרחב שמות המתנהג כמחלקת עצמים מובנית.

class מציג חבילה חדשה בדיוק כמו package, אך מסמן אותה כ־מחלקה: זמן הריצה מצרף בנאי בשם new, מאפשר את מילות המפתח field ו־method בתוך גופה, ותומך בירושה יחידה דרך התכונה :isa. זוהי התשובה ברמת שפת הליבה לתכנות מונחה־עצמים מבוסס bless שנכתב ידנית - אותה בעיה, ללא הקוד החוזר.

הערה

ניסיוני התכונה class (שם קוד Corinna) היא ניסיונית ב־Perl 5.42 ועשויה עוד להשתנות באופן בלתי תואם. יש להפעיל אותה עם use feature 'class' ולהשתיק את ההודעה עם no warnings 'experimental::class'.

תקציר#

class NAMESPACE;                          # rest-of-scope form
class NAMESPACE VERSION;
class NAMESPACE :ATTRIBUTES;
class NAMESPACE BLOCK                     # block form
class NAMESPACE VERSION BLOCK
class NAMESPACE VERSION :ATTRIBUTES BLOCK

מחלקה מינימלית עובדת:

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

מה מקבלים בחזרה#

class היא הצהרה, לא ביטוי - היא אינה מחזירה ערך הניתן להשמה. מה שמקבלים הוא:

  • חבילה חדשה בטבלת הסמלים, נגישה כמו כל חבילה אחרת.

  • בנאי new שנוצר אוטומטית. אין לכתוב new משלכם. הבנאי שנוצר מכבד את תכונות השדה :param ומריץ כל בלוק ADJUST בסדר ההצהרה.

  • $self כבול באופן מובלע בתוך כל גוף method.

  • __CLASS__ ניתן לשימוש בתוך מאתחלי שדה ומתודות כדי לציין את שם המחלקה הנבנית כעת (שעשויה להיות תת־מחלקה).

צורות#

class מקבלת ארבע צורות תחביריות. הפיצול בין צורות בלוק ל־משפט משקף את package בדיוק:

  • class NAME BLOCK - הגדרת המחלקה היא תוכן הבלוק. התיחום מסתיים בסוגר המסולסל הסוגר.

  • class NAME; - שאר התיחום הנוכחי (או הקובץ) עד למשפט class או package הבא הוא גוף המחלקה. שימושי לקבצים בעלי מחלקה יחידה.

  • class NAME VERSION ... - הגרסה מוצהרת כמו package Foo 1.234. הגרסה חייבת להקדים כל תכונה.

  • class NAME :ATTRIBUTES ... - תכונת מחלקה אחת או יותר, מופרדות ברווחים, כל אחת מתחילה ב־:. תכונת המחלקה היחידה המוגדרת כיום היא :isa.

class My::Thing 1.02 :isa(My::Base) {
    # body
}

שדות, מתודות, ADJUST#

בתוך גוף מחלקה שלוש מילות מפתח חדשות נמצאות בתיחום:

  • field מצהיר על אחסון לכל מופע. שדות מתנהגים כמו לקסיקליים בתוך מתודות אך לכל מופע יש משבצת משלו. התכונות :param, :reader ו־:writer מייצרות כבילת בנאי ומאחזרים.

  • method מצהיר על תת־שגרה הכובלת אוטומטית את $self ויכולה לקרוא ולכתוב את השדות העוטפים לפי שם. מתודות פועלות כאילו use feature 'signatures' בתוקף; $self אינו מופיע בחתימה.

  • ADJUST BLOCK רץ אחרי מאתחלי השדה, במהלך הבנייה, כאשר $self כבר כבול. יש להשתמש בו לאימות שלאחר הבנייה ולשדות נגזרים.

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

    ADJUST {
        die "step must be positive" if $step <= 0;
    }

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

שדות הם תמיד לקסיקליים למחלקה - אינם עוברים בירושה. תת־מחלקות הזקוקות לגישה למצב של הורה עושות זאת דרך מתודות, לא דרך שדות.

ירושה: :isa#

מחלקה יורשת ממחלקה אחת אחרת לכל היותר:

class Shape { ... }

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

נתמכת רק ירושה יחידה. יעד :isa נטען אוטומטית אם אינו כבר בזיכרון - שקול ל־use MODULE () לפני הצהרת המחלקה. ניתן לדרוש גרסה מינימלית באותו אופן ש־use MODULE VERSION עושה:

class Circle :isa(Shape 2.000) { ... }

:isa יורש רק מתודות. שדות הם אחסון פרטי לכל מחלקה; לתת־מחלקה אין תצוגה מובלעת אל שדותיו של הורה.

מצב גלובלי שהוא נוגע בו#

  • %INC - :isa(Parent) טוען את Parent דרך require בשימוש הראשון ורושם אותו כאן, בדיוק כמו use.

  • @ISA - מאוכלס עבור המחלקה מתוך התכונה :isa, כך שאינטרוספקציה קיימת של isa / UNIVERSAL / SUPER:: ממשיכה לעבוד.

  • טבלת הסמלים - שם המחלקה הופך ל־stash ב־%:: בדיוק כפי ש־package הייתה יוצרת אחד.

דוגמאות#

מחלקה פשוטה עם פרמטר נדרש ושדה נגזר:

class User {
    field $name :param;
    field $greeting;

    ADJUST { $greeting = "Hello, $name" }

    method greet { say $greeting }
}

User->new(name => 'Ada')->greet;          # Hello, Ada

ערכי ברירת מחדל לפרמטרים - = חל כאשר הקורא השמיט את הפרמטר; //= חל גם כאשר הערך היה undef; ||= חל גם כאשר הערך היה שקר:

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

ירושה יחידה עם דריסת מתודה:

class Animal {
    field $name :param;
    method speak { "some sound" }
    method introduce { say $name, " says ", $self->speak }
}

class Dog :isa(Animal) {
    method speak { "woof" }
}

Dog->new(name => 'Rex')->introduce;       # Rex says woof

מתודה פרטית דרך my method לקסיקלי, מופעלת דרך ->&:

class Safe {
    field $secret :param;

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

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

שימוש ב־__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

צורת המשפט שאר־הקובץ:

use v5.38;
use feature 'class';
class Config;

field $path :param;
method path { $path }
# File ends here; whole remainder is Config's body.

מקרי קצה#

  • אין להגדיר new. הבנאי שנוצר אוטומטית הוא נקודת הכניסה היחידה הנתמכת. sub new { ... } שנכתב בידי המשתמש בתוך גוף class הוא שגיאת זמן הידור.

  • אין ירושה מרובה. :isa(A, B) היא שגיאת תחביר. תכונות :isa מרובות במחלקה אחת גם נדחות.

  • אין מניפולציה של @ISA. יש להתייחס ל־@ISA כלקריאה בלבד עבור מחלקות שהוצהרו עם class. דחיפה אליו בזמן ריצה אינה מוגדרת.

  • package אינה יכולה לפתוח מחלקה מחדש. ברגע שמרחב שמות הוצהר עם class, משפטי package עוקבים המכוונים לאותו שם הם שגיאת זמן הידור, ולהפך.

  • שדות אינם לקסיקליים במובן המקובל. לא ניתן להתייחס לשדה מחוץ לגוף המחלקה, ולא ניתן להחיל עליו our או local. רק מתודות ובלוקי ADJUST רואים שדות.

  • $self הוא לקריאה בלבד בתוך מתודה. השמה אליו היא שגיאת זמן ריצה.

  • מאתחלי שדה רצים לפני שקיים $self. בתוך field $x = EXPR, ניתן להשתמש ב־__CLASS__ ובשדות שהוצהרו קודם, אך לא ב־$self. כל מה שדורש את המופע הבנוי במלואו שייך לבלוק ADJUST.

  • אין גישה לשדה לפני הצהרה. בתוך ביטוי מאתחל של שדה, רק שדות שהוצהרו קודם לכן בגוף המחלקה נראים.

  • פתרון מתודה. הניתוב עדיין עובר דרך ה־MRO הרגיל (UNIVERSAL::isa, SUPER::), כך ש־$self->SUPER::foo(@args) עובד כפי שהוא עובד עבור מחלקות מבוססות bless.

  • תיחום צורת המשפט. class Foo; צורך את שאר הבלוק העוטף - בדרך כלל שאר הקובץ. class שני או package מסיים אותו; אין דרך ״לסגור״ מחלקה בצורת משפט מוקדם.

  • אזהרת ניסיוני. כל הצהרת class מנפיקה אזהרת class is experimental תחת האזהרות בברירת מחדל. יש להשתמש ב־no warnings 'experimental::class' ברגע שהתכונה בשימוש אמיתי.

הבדלים מהמקור#

תואם במלואו ל־Perl 5.42 במקור. התכונה מסומנת כניסיונית במקור; pperl משקף את הסטטוס הזה ומנפיק את אותה קטגוריית אזהרה (experimental::class).

ראו גם#

  • field - מצהיר על אחסון לכל מופע בתוך גוף class; תומך ב־:param, :reader, :writer

  • method - מצהיר על תת־שגרה עם $self מובלע וגישה ישירה לשדות המחלקה העוטפת

  • package - הצהרת מרחב השמות שאינה מחלקתית; class הוא האח המודע לתכנות מונחה־עצמים בעל אותן צורות תחביריות

  • bless - הדרך עד גרסה 5.38 לצרף מחלקה להפניה; עדיין נתמך, ומשתלב עם עצמים בנויי class לבדיקות isa/can

  • isa - אופרטור לבדיקת חברות במחלקה; עובד גם עבור עצמים שהוצהרו עם class וגם עבור עצמים מבוססי bless

  • use - כיצד :isa(Parent) טוען את מחלקת ההורה מאחורי הקלעים