בנית-אתרים.com – בלוג בנית אתרים
בקטגוריה: פרוייקטים|תכנות מונחה עצמים
17 מרץ 2010שים לב: הפוסט הזה מסתמך על ידע מפוסטים קודמים בסדרה, לאינדקס הפוסטים של תכנות מונחה עצמים.
כימוס הוא נושא די פשוט מבחינה תחבירית ומבחינת שימוש אך יש לו שימושים רבים ולעתים מעט מפולפלים.
הוא מהווה אבן יסוד חשובה ביותר בתכנות מונחה עצמים והגנה על מידע בתוך אובייקט.
המונח כימוס מתייחס ליכולת של מחלקות ליצור מאפיינים ופעולות שמוסתרות ומסתירות מימוש של חלקים מסויימים בעזרת הוראות בודדות.
במילים אחרות, אפשר ליצור צד ציבורי (public) וצד פרטי (private) כך שבתוך האובייקט והמחלקה ניתן לגשת לשניהם אך מבחוץ רק לצד הציבורי.
ישנן 3 רמות שונות של גישות:
עד עכשיו בכדי ליצור מאפיין למחלקה רשמנו את המילה var ואחריה את שם המאפיין ופעולות רשמנו כמו שאנו רושמים פונקציות מחוץ למחלקה.
מעכשיו והלאה לפני מאפיינים נרשום public/private/protected במקום המילה var ולפני כל פעולה נרשום public/private/protected.
לעתים נרצה שמידע כלשהו שקיים באובייקט ישמר ויועבר אך אנו לא רוצים שיהיה אפשר לגשת אליו מבחוץ מסיבות כמו אבטחת מידע, שמירה על תקינות נתונים, החזקת נתונים זמניים וכדומה.
בנוסף יהיו מצבים שפעולות מסויומות יהיו קוד פנימי כלשהו, שאנו לא רוצים שיהיה זמין אך הוא כן דרוש למספר פעולות במחלקה, לדוגמה קטעים לוגיים המשותפים למספר פעולות שאנו רוצים לפרק לחלקים ובכך לקצרן, פעולות שנועדו לרוץ בזמנים מסויימים ועוד.
ניתן דוגמה לשימוש כמעט אמיתי של מחלקות ושל כימוס, נכתוב מחלקה של משתמש קונה באתר קניות ושל מוצר באתר קניות. מטרתנו היא לכתוב מחלקה שבהינתן username וpassword של משתמש היא מאפשרת לנו למצוא מידע עליו, לבצע פעולת רכישה ולעדכן את שמו. ובנוסף מחלקה שמייצגת מוצר כלשהו, שבהינתן id היא מוצאת את מחיר ושם המוצר, אובייקטים שלה מועברים לפעולת הרכישה.
נניח שקיים כבר חיבור למסד נתונים mysql וקיימת טבלה Customers שמכילה עמודות של id, username, password, fullname, credit_card וטבלת Products שמכילה עמודות של id, name, price.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | class Product { public $id; public $name; public $price; public function __construct($id) { $this->id = (int)$id; $query = mysql_query("SELECT name, price FROM Products WHERE id = '{$this->id}' LIMIT 0,1"); $query = mysql_fetch_array($query); $this->name = $query['name']; $this->price = $query['price']; } } class Customer { private $data = array(); public function __construct($username, $password) { $username = mysql_real_escape_string($username); $password = md5($password); $query = mysql_query("SELECT id, username, password, full_name, credit_card FROM Customers WHERE username = '{$username}' AND password = '{$password}' LIMIT 0,1"); $this->data = mysql_fetch_array($query); } private function credit_card_valid() { // Make sure there are enough digits and all this stuff return $is_vaild; } public function buy(Product $prod) { // First make sure the credit card is valid if($this->credit_card_valid()) { // Call an imiginary function which takes money from a credit card make_a_payment_using_credit_card($this->data["credit_card"], $prod->price); // Submit an email to the site administrator that a new product has been bought let_admin_know_by_email($prod->id); return "Thank you ".$this->full_name.", for buying at our shop. The admin will contact you soon."; } return false; } public function update_name($new_name) { $this->data["full_name"] = $new_name; $new_name = mysql_real_escape_string($new_name); mysql_query("UPDATE Customers SET full_name = '{$new_name}' WHERE id = '{$this->data['id']}'"); } public function full_name() { return $this->data["full_name"]; } } // Lets look for the user yossi22 $yossi = new Customer("yossi22", "123abc"); // Excpected to print something like "Yossi Avraham" echo $yossi->full_name(); // We are now using the product class, and look for the product with the id 12345 $new_television = new Product(12345); // Lets make Yossi buy a new television // If he gets it let him know $ret = $yossi->buy($new_television) if($ret) { // Will out put "Thank you Yossi Avraham, for buying at our shop. The admin will contact you soon." echo $ret; } // Maybe Yontan want a new TV two? $yonatan = new Customer("yonatan1", "976431"); // Wait! he changed his name to Ohad Regev $yonatan->update_name("Ohad Regev"); $ret = $yonatan->buy($new_television) if($ret) { // Will out put "Thank you Ohad Regev, for buying at our shop. The admin will contact you soon." echo $ret; } |
בואו ננסה להבין קטע אחרי קטע:
ראשית כתבנו מחלקת Product המכילה 3 משתנים ציבוריים id, name וprice – מספר סידורי, שם ומחיר ולה פעולת Constructor ציבורית שמקבלת id ומוצאת את המוצר שפרטיו תואמים לid זה.
שנית הצהרנו על מחלקה בשם Customer, היא מכילה משתנה פרטי בשם data, פעולת Constructor ציבורית המקבל שם משתמש וסיסמה, פעולת רכישה ציבורית המקבלת מוצר (שימו לב לארגומנט הראשון, ניתן להגדיר את סוג המחלקה שאנו מצפים לקבל) ומשתמשת בפעולה פרטית המוודאת תקינות של כרטיס אשראי, פעולה ציבורית לעדכון השם ופעולה ציבורית להחזרת השם. לא אכנס לתוכן כל פעולה כיוון שהן די פשוטות וזניחות.
לאחר מכן יצרנו אובייקט בשם yossi שהוא מופע של המחלקה Customer והעברנו שם משתמש וסיסמה, הדפסנו את השם המלא של יוסי. כעת ניצור אובייקט new_television שהוא מופע של המחלקה Product עם הid שמספרו 12345.
לאחר מכן נגיד ליוסי לקנות טלוויזיה חדשה, ואם הוא הצליח לקנות אז נדפיס הודעה שמכילה את השם שלו.
מאוחר יותר ביצענו תהליך דומה למשתמש אחר בשם יונתן, ששינה את שמו לאוהד. לאחר שקנה טלוויזיה חדשה תודפס לו הודעה המכילה את שמו החדש.
כעת נבדוק מה קורה כאשר אנו מנסים לגשת לקוד שמוגדר כפרטי – private.
נוסיף את הקוד הבא לאחר ההצהרה על המשתנה yossi:
print_r($yossi->data);
כמו שוודאי קראתם המאפיין data הוא פרטי ולכן לא ניתן לגשת אליו מחוץ למחלקה עצמה. נסיון לגשת אליו תגרור פלט הדומה לפלט הבא:
במילים פשוטות, הסקיפט יעצר באותה הנקודה כיוון שלא ניתן לגשת למאפיינים ופעולות פרטיות.
נסיון לגשת לפעולה הפרטית credit_card_valid יוביל לתוצאה דומה, עבור הקוד:
$yossi->credit_card_valid();
נקבל פלט שכזה:
במחלקה הגנו על מספר נתונים: על מספר כרטיס האשראי, על שם המשתמש ועל הסיסמה. הם אינם נגישים מחוץ לאובייקט אך כן בתוכו.
בנוסף יצרנו פעולה שמחזירה את המידע מהמאפיין הפרטי, לפעולות שכאלו נהוג לקרוא getters, לפעולות שמקבלות מידע ומכניסות אותו למשתנה (בדומה לפעולה update_name) נהוג לקרוא setters.
השתמשנו בפעולה פרטית בשם credit_card_valid בכדי לוודא לפני רכישה האם מספר הכרטיס קיים ואמיתי.
אני מקווה שהדוגמה הבהירה לכם באילו מצבים יש להשתמש במאפיינים ופעולות ציבוריים ובאילו מקרים יש להשתמש בפרטיים, כמובן שכל זאת נתון לשיקול דעתכם.
שמי שי ואני בונה אתרים וכיום חותך (מקודד) ומתכנת.
אני עובד אל מול התקן העולמי של W3C, ומתכנת בשפת PHP תוך שימוש בטכנולוגיות עדכניות, תוך שמירה על קוד שניתן לתחזוקה.
בין כישורי נמנים: PHP, SQL, (X)HTML, CSS (2-3), XML, JavaScript ( + jQuery) ועוד.
4 תגובות לכימוס – Encapsulation
יונתן
18 מרץ, 2010 בשעה 7:45
מאמר מעולה, מחכה למאמר הבא
איתי סלע
20 מרץ, 2010 בשעה 12:04
בהחלט מאמר טוב. הייתי רוצה להציע מספר שינויים:
__construct – אני באופן אישי לא משתמש בזה. לפעולת הבנאי ניתן לקרוא גם בשם המחלקה והתוצאה זהה (אם אני לא טועה – __construct שייך לPHP 4).
ממליץ בחום לכתוב את המימוש של Product. מחלקה פשוטה אך עלולה ליצור חוסר הבנה למי שמנסה להבין את הקוד ולא בדיוק מצליח לדמיין איך היא פועלת.
תדגים ניסיון לגשת למשתנים ופונקציות פרטיות ותכתוב במודגש שהדבר יוצא שגיאה ! (תמיד טוב להבין מקודים לא טובים לעומת הטובים).
מדגיש את הנקודה מהמאמר הקודם: תכתוב שבהמשך תסביר מדוע צריך לכתוב כל כך הרבה קוד בשביל פעולות שאפשר לעשות בדרכים עקיפות (וקצרות יותר בעולם האמיתי). זו הנקודה הכי חלשה אצל מתכנתים שנכנסים לתחום הOOP.
כל הכבוד
Shay | Exalted Web
20 מרץ, 2010 בשעה 12:36
תודה על התגובה
הוספתי מימוש של Product וקטע על גישה לקוד פרטי כמו שהצעת, לא מזיק.
למען האמת גם אני לרוב לא משתמש ב__construct (שנוסף דרך אגב בPHP5 ולא ב4) אלא בשם המחלקה, יותר נוח לי, אם כי יש יתרונות לשימוש בשם __construct.
לאחר המאמר הבא שעוסק בירושה יהיה יותר קל להסביר מדוע OOP באמת עוזר ומקל ולתת דוגמאות פרקטיות מתוך קטעי קוד אמיתיים.
חיי
1 מאי, 2010 בשעה 1:02
היי שי,
המאמר מרשים , אך ההיתי שמח אם ההית מתייחס לכמה נקודות:
א. תשתדל לרשום את הערות בקוד בעברית , הרי אתה פונה אל קהל יעד שדובר עברית (היות והאתר והמדריכים כתובים בעברית)
ב. תנסה לסדר את הפס גלילה בקטעי הקוד , לא ממש נוח לקרוא את הקוד בצורה הזאת.
ג. שאתה מסביר על משתנה , תציין את הערך שלו או את החלק הרלוונטי בקוד, לא נוח לעלות כל רגע ולחפש בקוד על מה אתה מדבר.
בכל אופן, מאמר יפה כל הכבוד , באמת שחסרים מדריכים כאלו בעברית.