בואו נדבר על Public Data Members

06/06/2018

דוד בהט סיפר בגיקטיים על אהבתו ל Code Reviews דרך סיפור על דיון בנושא Public Data Members מול Properties. בואו נראה את הפרטים הטכניים של התלבטות כזו ואולי זה יעזור גם לכם להחליט.

1. מהם Public Data Members

מחלקות מסוימות כוללות כחלק מה API שלהן גם אפשרות לגשת לשדות מידע. נתבונן בתוכנית C# הבאה הכוללת מחלקה עבור צומת בעץ בינארי ומאפשרת לקוד חיצוני לקרוא ולשנות את הילדים השמאלי והימני של אותו צומת:

using System;

public class Node {
  public Node left;
  public Node right;
  public int value;

  public Node(int aValue) {
    value = aValue;
  }
}

class MainClass {
  public static void Main (string[] args) {
    Node n = new Node(10);
    n.left = new Node(5);
    n.right = new Node(30);
    n.right.right = new Node(40);
    n.right.left = new Node(20);

    Console.WriteLine ("Value: " + n.left.value);
  }
}

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

כשאתם כותבים מחלקה ומחליטים מה יהיה הממשק החיצוני שלה לעולם עליכם להחליט איזה חלקים בממשק ייכתבו כמו הפעלת פונקציה ואיזה חלקים ייכתבו כמו כתיבה וקריאה ממשתנים.

בשפות מסוימות (היוש Java) החלטה זו גוררת גם השלכה נוספת והיא שבתחביר ה Public Data Members לא ניתן להריץ קוד מתוך המחלקה שישלוט בכתיבה או בקריאה למשתנה מסוים. ברוב השפות לעומת זאת אין קשר בין הדברים ואפשר להוסיף קוד שירוץ בעת כתיבה כזו גם במועד מאוחר יותר.

2. הפיכת Public Data Member ל Property

שדות מידע שכתיבה אליהם או קריאה מהם גוררת הפעלת קוד שלנו נקראים Properties. המעבר משדה מידע ציבורי ל Property הוא שקוף מבחינת הקוד החיצוני שמשתמש במחלקה.

ניקח את הדוגמא שראינו ונוסיף לה מנגנון שיאפשר לכל צומת לדעת מי ההורה שלו. בכל פעם שנוסיף צומת בתור ילד של צומת אחר נעדכן שדה מידע חדש שנקרא parent. הקוד הבא עבור המחלקה Node כולל את השינוי:

public class Node {
  public Node left {
    get { return _left; }
    set { 
      _left = value;
      _left.parent = this;
    }
  }
  public Node right {
    get { return _right; }
    set { 
      _right = value;
      _right.parent = this;
    }
  }

  public Node parent;
  public int value;

  private Node _left;
  private Node _right;

  public Node(int aValue) {
    value = aValue;
    parent = null;
  }
}

ההחלפה מ Public Data Member ל Property היתה שקופה לחלוטין לקוד חיצוני. אותו הקוד ב Main שעבד קודם ימשיך לעבוד ואפילו יכול עכשיו לגשת לשדה parent:

class MainClass {
  public static void Main (string[] args) {
    Node n = new Node(10);
    n.left = new Node(5);
    n.right = new Node(30);
    n.right.right = new Node(40);
    n.right.left = new Node(20);

    Console.WriteLine ("Value: " + n.left.parent.value);
  }
}

3. מתודות ציבוריות

מנגנון נוסף שאפשר להשתמש בו בבניית ממשק של מחלקה הוא מתודות ציבוריות. זה מנגנון שאין סביבו הרבה התלבטויות אבל בשביל השלמות חשבתי להוסיף גם אותו לדוגמא. המתודה sumWithChildren שנוספה עכשיו לצומת סוכמת את ה value של צומת נתון וכל הילדים שלו:

public class Node {
  public Node left {
    get { return _left; }
    set { 
      _left = value;
      _left.parent = this;
    }
  }
  public Node right {
    get { return _right; }
    set { 
      _right = value;
      _right.parent = this;
    }
  }

  public Node parent;
  public int value;

  private Node _left;
  private Node _right;

  public Node(int aValue) {
    value = aValue;
    parent = null;
  }

  public int sumWithChildren() {
    return (
      (this.left != null ? this.left.sumWithChildren() : 0) +
      (this.right != null ? this.right.sumWithChildren() : 0) +
      this.value
    );
  }
}

ומאחר וזו מתודה רקורסיבית קל לראות שאנחנו מפעילים אותה עם סוגריים עגולים אחרי שם המתודה, בשונה מהמנגנונים הקודמים שראינו שם התחביר היה נראה כמו כתיבה למשתנה.

4. אז רגע: צריך להגדיר שדות מידע כ Private?

זה תלוי. בשפה שתומכת ב Properties אין שום ערך בהגדרת שדות המידע כפרטיים וגישה אליהם דרך Property שרק ניגש לשדות. זה ממילא מה שקורה כשאתם כותבים שדה מידע ציבורי. כשתצטרכו להריץ קוד אחר בעת כתיבה או קריאה לשדות אלו פשוט תחליפו משדה מידע ציבורי ל Property בתוך המחלקה ולא תצטרכו לשנות כלל קוד חיצוני.

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

גם בשפה שתומכת ב Properties חשוב לזכור שלא כל שדה מידע הוא חלק מהממשק של המחלקה. בתכנות מונחה עצמים יש סודות וטוב שיהיו. ככל שהממשק של המחלקה קטן יותר כך יהיה לכם יותר קל לשנות את דעתכם ולמחוק או לשנות חלקים מקוד המחלקה בלי שתצטרכו לשנות קוד חיצוני שתלוי בה. שדות מידע פרטיים (שאולי יהפכו ל Properties פרטיים אם יהיה צורך) ומתודות פרטיות הן דרך טובה לארגן קוד בתוך המחלקה בלי להשפיע על הממשק. תמיד אפשר להוסיף אותם וקל למחוק אותם.

כלל אצבע טוב הוא שאם הדבר הכרחי לממשק החיצוני של מחלקה יש להגדירו כ Public ואת כל השאר כדאי להגדיר כ Private כדי שיהיה לכם קל לשנות בהמשך. הרבה יותר קל להפוך משהו מ Private ל Public מאשר להיפך.