תכנות מונחה עצמים (OOP) -חברי מחלקה סטאטיים

סגור באמצעות טופס זה תוכלו לספר ולהמליץ לחבריכם..
שם השולח:
כתובת דוא"ל של השולח:
שם המקבל:
שלח לכתובת דוא"ל:
הוסף הערה:
חברי מחלקה סטאטיים משויכים למחלקה ולא לאובייקט, בפרק זה נסביר את השימוש בחברי מחלקה סטאטיים (Static Data Members) ובמתודות סטאטיות (Static Methods).

 חברי מחלקה סטאטיים  




 
מאת: ארז קלר

הורדת דוגמאות קוד


Static Data Member

עד כה הגדרנו תכונות (Data Members) השייכות לאובייקט.
בכל יצירת מופע (Instance) של אובייקט נוצרים בזיכרון (ב- Manage heap) העתקים של אותם המשתנים החברים, לכל אובייקט יש Data Members משל עצמו ,אין שום קשר בין ה- Data Members של אובייקט אחד ל- Data members של האובייקטים האחרים מאותה המחלקה.
במילים אחרות, אם נגדיר מספר אובייקטים מהמחלקה מסויימת, לכל אובייקט יוקצו בזיכרון כל  ה- Data Members, המוגדרים במחלקה, תוכנם של אותם המשתנים יהיה שונה מאובייקט לאובייקט.

לעיתים נרצה לנהל מאגר נתונים משותף לכל האובייקטים הנוצרים ממחלקה מסוימת, הסיבה לכך יכולה להיות חיסכון בזיכרון או ביצוע אלגוריתם מסוים.
לדוגמה, נניח שברצוננו למנות את מספר האובייקטים שנוצרו ממחלקה מסוימת מאז שהתוכנית החלה לרוץ או בתכונה אחרת נשתמש בערך זהה לכל האובייקטים.
במקרים אלו, ללא השימוש ב- Static Data Members יתכן ותיווצר בעיה כפי שהיא מוצגת בדוגמה הבאה (StaticDataMember01):

 1  : class Circle 
 2  : { 
 3  :     public int x; 
 4  :     public int y; 
 5  :     public int radius; 
 6  :     public double PI = 3.1415; 
 7  :     public int counter; 
 8  :     public Circle(int x, int y, int radius) 
 9  :     { 
 10 :         this.x = x; 
 11 :         this.y = y; 
 12 :         this.radius = radius; 
 13 :         this.counter++; 
 14 :     } 
 15 :     public void Print() 
 16 :     { 
 17 :         Console.WriteLine("X={0}, Y={1}, Radius {2}, Counter={3}", x, y, radius, counter); 
 18 :     } 
 19 : } 
בשורה 6 מוגדרת התכונה PI, ל-PI ערך קבוע שאינו משתנה לעולם.
בשורה 7 מוגדרת התכונה counter, באמצעותה נרצה לדעת כמה אובייקטים מהמחלקה Circle נוצרו במהלך ריצת התוכנית.

Main:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Circle c1 = new Circle(1, 2, 100); 
 6  :         Circle c2 = new Circle(3, 4, 120); 
 7  :         Circle c3 = new Circle(4, 5, 130); 
 8  :         c1.Print(); 
 9  :         c2.Print(); 
 10 :         c3.Print(); 
 11 :     } 
 12 : } 
פלט:
פלט של דוגמת הקוד StaticDataMember01

בתוכנית נוצרו 3 אובייקטים מהמחלקה Circle, מדוע מודפס רק 1?
דוגמה רעה, נבדוק מדוע.
ביצירת האובייקט הראשון (c1) מופעל הבנאי שמאתחל את ערכי התכונות (לא בודק את ערכי הפרמטרים וזה כבר רע) ומעלה את התכונה counter ב-1.
ביצירת האובייקט השני (c2) מופעל הבנאי שמאתחל את ערכי התכונות ומעלה את התכונה counter ב-1.

גם ביצירת האובייקט השלישי (c3) מופעל הבנאי שמאתחל את ערכי התכונות ומעלה את התכונה counter ב-1.
הבעיה היא שלכל אחד מהאובייקטים יש counter עצמאי.
 בעיה נוספת היא בזבוז זכרון, התכונה PI מכילה ערך זהה בכל האובייקטים, גם אם נגדיר עוד מאות אלפי או מיליוני מופעים מהמחלקה Circle ערכה לא ישתנה, אז מדוע צריך להגדיר אותה מחדש בכל פעם?
תמונת הזיכרון אחרי הקצאת שלושת האובייקטים:

תמונת הזיכרון אחרי הקצאת שלושת האובייקטים
מה רואים באיור:
בכל יצירת מופע (Instance) של אובייקט נוצרים בזיכרון (ב- Manage heap) העתקים של התכונות, לכל אובייקט יש תכונות פרטיות משל עצמו ,אין שום קשר בין התכונות של אובייקט אחד לתכונות של האובייקטים האחרים מאותה המחלקה.
במילים אחרות, אם נגדיר מספר אובייקטים מהמחלקה Circle, לכל אובייקט יוקצו בזיכרון כל התכונות המוגדרות במחלקה, תוכנם (הערך) של אותן התכונות יהיה שונה מאובייקט לאובייקט.
 ו-counter הוא לא יוצא דופן.
מבחינת תמונת הזיכרון עולה וצפה האיוולת השנייה, התכונה PI ערכה קבועה כבר אלפי שנים (וכנראה גם לא תשתנה בקרוב),
בכל הקצאת אובייקט אנו מבזבזים זיכרון לחינם משום שאין טעם להקצותה כל פעם, מספיקה פעם אחת בלבד.


הפיתרון המתבקש, הגדרת PI ו- Counter כתכונות סטאטיות (StaticDataMenber02):

 1  : class Circle 
 2  : { 
 3  :     public int x; 
 4  :     public int y; 
 5  :     public int radius; 
 6  :     public static double PI = 3.1415; 
 7  :     public static int counter; 
 8  :     public Circle(int x, int y, int radius) 
 9  :     { 
 10 :         this.x = x; 
 11 :         this.y = y; 
 12 :         this.radius = radius; 
 13 :         Circle.counter++; 
 14 :     } 
 15 :     public void Perimeter() 
 16 :     { 
 17 :         Console.WriteLine("Perimeter = {0}", 2 * this.radius * Circle.PI); 
 18 :     } 
 19 :     public void Print() 
 20 :     { 
 21 :         Console.WriteLine("X={0}, Y={1}, Radius {2}, Counter={3}", x, y, radius, Circle.counter); 
 22 :     } 
 23 : } 
הפעם, בשורות 6 ו-7 נגדיר את התכונות PI ו-counter כתכונות סטאטיות (Static Data Member).
בשורה 13, בתוך הבנאי ניתן לראות התייחסות לתכונה הסטאטית, ההתייחסות היא באמצעות שם המחלקה ולא this.
סיבה? תכונה סטאטית היא ברמת המחלקה ולא ברמת האובייקט.
הפעם שנריץ את התוכנית (Main לא השתנתה) נקבל את הפלט הבא:
תמונת הפלט של התוכנית StaticMember02
הפעם counter מכיל את הערך הנכון.
נבחן את תמונת הזכרון הפעם:
תמונת זכרון עם שימוש בתכונות סטאטיות

הפעם התכונות PI ו- counter מוגדרות כתכונות סטאטיות ולכן הן מוגדרות ברמת המחלקה ולכן הן תכונות משותפות לכל האובייקטים מהמחלקה Circle.
חסכנו מקום בזיכרון ומנענו את השגיאה הלוגית בתכונה counter.
הערה – השימוש ב- this בפנייה לתכונות המופע ובשם המחלקה Circle בפנייה לתכונות הסטאטיות אינו חובה במקרה זה, אולם מומלץ בשל שיפור בהירות הקוד.
 
אורך החיים של תכונות סטאטיות
מכיוון שתכונות סטאטיות מוגדרות ברמת המחלקה ולא ברמת האובייקט הן נוצרות כאשר המחלקה נטענת לזיכרון עם תחילת ריצת התוכנית (לרוב).
ולכן ניתן לגשת אליהן לפני שנוצר אובייקט מהמחלקה ובלי קשר לקיומו.


Static Method
  

Static Methods דומות ל- Static Data Members בכך שהן אינן שייכות לאובייקטים אלא הן ברמת המחלקה. Static Methods זמינות מהרגע שהמחלקה נטענת לזיכרון.
כמו Static Data Members גם Static Methods מוגדרות באמצעות המילה השמורה static , תחביר:
class Sample
{
     . . .
     public static void DoSomething()
     {
                        . . .
     }
     . . .
}

 
מכיוון שהן משויכות למחלקה ולא למופע (instance) מסוים מהמחלקה, הן מופעלות באמצעות שם המחלקה:
ClassName.StaticMethodName (. . . );
 
מכיוון ש- Static Methods זמינות מהרגע בו המחלקה נטענת לזיכרון, ולפני הקצאת האובייקט הראשון הן יכולות לטפל אך ורק ב- Static Data Members.
Static Data Members אינן מקבלות את הייחוס this .
 
דוגמה (StaticMethodSample):

 1  : static class MyMath 
 2  : { 
 3  :     public static int Sum(int n1, int n2) 
 4  :     { 
 5  :         return n1 + n2; 
 6  :     } 
 7  :     public static int Sub(int n1, int n2) 
 8  :     { 
 9  :         return n1 - n2; 
 10 :     } 
 11 :     public static float Avg(int n1, int n2) 
 12 :     { 
 13 :         return (n1 + n2) / 2; 
 14 :     } 
 15 : } 

בשורות 3,7,11 - הגדרת Static Methods מתבצעת באמצעות המילה השמורה static.
בשורה 1 הגדרנו את כל המחלקה כסטאטית, הדבר אפשרי משום שהיא מכילה רק מרכיבים סטאטיים,
במידה והיו בה תכונות או מתודות שאינן סטאטיות, הדבר לא היה אפשרי.

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Console.WriteLine("{0} + {1} = {2}", 10, 20, MyMath.Sum(10, 20)); 
 6  :         Console.WriteLine("{0} - {1} = {2}", 100, 200, MyMath.Sub(100, 200)); 
 7  :         Console.WriteLine("Avg of {0} and {1} = {2}", 1000, 2000, MyMath.Avg(1000, 2000)); 
 8  :     } 
 9  : } 
בשורות 5,6,7  - הפעלת ה- Static Methods של המחלקה MyMath באמצעות שם המחלקה.

פלט:
StaticMethodSample
 

בנאים סטאטיים

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

לדוגמה:

 1  : class Circle 
 2  : { 
 3  :     private int m_X; 
 4  :     private int m_Y; 
 5  :     private int m_Radius; 
 6  :     private static double PI; 
 7  :     static Circle() 
 8  :     { 
 9  :         PI = 3.1415; 
 10 :     } 
 11 :     public Circle(int x, int y, int radius) 
 12 :     { 
 13 :         this.m_X = x; 
 14 :         this.m_Y = y; 
 15 :         this.m_Radius = radius; 
 16 :     } 
 17 :      . . . 
 18 : } 
בשורה 7 מוגדר בנאי סטאטי.

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