MasterScripter

עקרון ההחלפה של ליסקוב

אם זה נראה כמו ברווז, מגעגע כמו ברווז, אבל דורש בטריות – אתה משתמש באבסטרקציה שגויה

היא נחשבת לפורצת דרך עבור נשים – היא מהראשונות לקבל תואר דוקטור למדעי המחשב (1968) ואחת משלוש בלבד שזכו בפרס טיורינג (2008) ב – 49 השנים בהן הוא מחולק.
ברברה ליסקוב (נולדה ב – 1939) תרמה רבות לעולם המחשוב, בפרט לתחום התכנות מונחה עצמים: מעל למאה מאמרים, שלושה ספרים, פיתוח מתודולוגיות, שפות תכנות ומערכות הפעלה ועוד.
אחד המאמרים המפורסמים ביותר שלה הוא גם העקרון השלישי בחמשת העקרונות הראשונים לעיצוב תוכנה מונחית עצמים: עקרון ההחלפה של ליסקוב.

לפני שנצא לדרך, הקדמה למצטרפים החדשים:
הפוסט הזה הוא פוסט שלישי בסדרת S.O.L.I.D / posts שבה אני מפרט על חמשת העקרונות הראשונים לעיצוב תוכנה מונחית עצמים.
אני ממליץ לקרוא על שני העקרונות הראשונים לפני שממשיכים לעקרון השלישי, שכן הם הכרחיים כדי ליישם אותו כמו שצריך:

שנתחיל?

הגדרת הקשר בין תת-סוג לסוג האב

ברברה ליסקוב הציגה לראשונה את עקרון ההחלפה (באנגלית: Liskov Substitution Principle) בשנת 1987.
המאמר שבו העקרון הוצג נכתב בשפה מאד טכנית ויבשה, עד כדי כך שאפילו בקהיליית מדעני המחשב התקשו לאמץ את העקרון:

אם עבור כל אובייקט o1 מסוג S ישנו אובייקט o2 מסוג T כך שעבור כל תוכנה P המוגדרת במונחים של T, ההתנהגות של P לא תשתנה כאשר o2 יוחלף ב – o1,
אז S הינו תת-סוג של T

אני הולך להמר שלפחות 80% מהקוראים לא הצליחו להבין מה מתרחש בנוסחה למעלה וזה בסדר: לפני שלמדתי את העקרון, גם אני לא הצלחתי להבין מה הגברת ליסקוב רוצה מחיי.
כשאני חושב על זה, זה מעט אירוני: עקרון ההחלפה של ליסקוב, לטעמי, הוא העקרון הפשוט ביותר מבין חמשת העקרונות.
למזלנו, העקרון נוסח מחדש על-ידי רוברט מרטין, ככה שיהיה (יותר) ידידותי לקורא.
לפני שאני מציג בפניכם את ההגדרה, חשוב לי ליישר קו ולוודא שאתם מכירים את המונחים "מחלקת בסיס" ו – "מחלקה נגזרת".

בקטע הקוד הבא:

מתוארות שתי מחלקות – A ו – B.

  • A – מחלקת בסיס: מחלקה ממנה יורשת מחלקה אחרת (B)
  • B – מחלקה נגזרת: מחלקה שיורשת ממחלקה אחרת (A)

עבור קטע הקוד למעלה, ניתן לומר כי המחלקה B  היא מחלקה נגזרת ממחלקת הבסיס A.

עכשיו כשהמונחים הוגדרו, אני יכול לשתף איתכם ללא חשש את התרגום לניסוח-מחדש של עקרון ההחלפה של ליסקוב:

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

עקרון ההחלפה של ליסקוב: על ריבועים ומלבנים

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

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

וכאן אנחנו נתקלים בבעיה. ויקיפדיה מגדירה ריבוע:

ריבוע הוא מרובע בעל ארבע צלעות שוות

מחלקת המלבן מאפשרת למשתמש להגדיר רוחב וגובה שונים; כלומר צלעות לא-שוות, בעוד שכל צלעות הריבוע שוות זו לזו.
כדי להתגבר על הבעיה, נדרוס את הפונקציות setWidth ו – setHeight כשניישם את המחלקה Square:

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

אבל מה יקרה כשנכתוב פונקציה שמטפלת בטיפוס נתונים Rectangle? (רמז: בניינים יתחילו להתמוטט..)

ניקח לדוגמה את הפונקציה הבאה, שמקבלת כפרמטר עצם מסוג Rectangle, קובעת רוחב (5) וגובה (4) ואז בודקת באמצעות הפקודה assert האם רוחב כפול גובה שווה 20:

זכרו: על-פי עקרון ההחלפה של ליסקוב, על הפונקציה לדעת להתמודד עם כל עצם מסוג Rectangle!

מי שכתב את הפונקציה g הניח שבעת שינוי רוחב של מלבן, הגובה נשאר כפי שהוא ולהפך.
על-פניו, הנחה הגיונית וסבירה – אבל ברגע שנעביר לפונקציה Square ולא Rectangle – הפונקציה תחזיר ערך שגוי.

המחלקה Square (המחלקה הנגזרת) משנה את ההתנהגות הטבעית של המחלקה Rectangle (מחלקת הבסיס).
המשפט האחרון, גבירותיי ורבותיי, הוא תמצית עקרון ההחלפה של ליסקוב:

מחלקה מרחיבה לעולם לא תשנה את ההתנהגות הטבעית של מחלקת הבסיס

איך נפתור את הבעיה? לצערי, אני נאלץ להודיע לכם שאין פתרון קסם!

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

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

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

במאמרו, רוברט מרטין כתב (בתרגום חופשי): תקינות איננה פנימית. מה הכוונה?
אם נבחן בנפרד כל אחת מהמחלקות, Rectangle ו – Square, נגלה שבשורה התחתונה, שתיהן מתפקדות כצפוי:

  • Rectangle מאפשרת לנו לקבוע רוחב וגובה למלבן
  • Square מאפשרת לנו לקבוע אורך צלע

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

כלל האצבע

כשלמדתי על עקרון ההחלפה של ליסקוב, נתקלתי בכלל האצבע הבא:

מחלקות ממומשות צריכות להיות סופיות

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

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

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

מקורות

אם הגעת עד לפה, הגיע הזמן להירשם לניוזלטר!

Summary
Article Name
עקרון ההחלפה של ליסקוב
Description
עקרון ההחלפה של ליסקוב הוא השלישי מתוך חמשת העקרונות הראשונים לעיצוב תוכנה מונחה-עצמים שיישומם מביא לכתיבת קוד שריד יותר שיחזיק מעבר לגרסה הראשונה.
Author
Publisher Name
MasterScripter
Publisher Logo
אודות 
hello world! אני יונתן, מתעסק בתכנות ופיתוח WEB פלוס מינוס מאז שאני זוכר את עצמי. ב2013 הקמתי את קבוצת סיזן ביחד השותף היקר שלי - שלומי שלומקה זק. ביום מתעסק בפיתוח ועיצוב אתרי אינטרנט ואפליקציות, עיצוב חוויית משתמש, הקמת מיזמים, ועוד..אבל מה שלא ידעתם עליי - זה שבלילה אני באטמן. אל תגלו לאף אחד!

3 תגובות

  1. יותם

    24 במאי 2017 - 18:01
    תגובה

    היי,
    קצת צורם לי שהמקרה הכי קלאסי להורשה (ריבוע הוא מרובע – בעברית זה מבלבל), צריך למנוע ממנו הורשה בגלל פונקציית בדיקה מאוד מוזרה (שבעצם בודקת את מנגנון ההשמה, שליפה או כפל?).
    במידה ומכניסים את פונקציית חישוב השטח לתוך האובייקט נפתרה הבעיה (דבר שנראה לי מתבקש).
    לי נראה שכל הורשה אפשר להתקיל ככה(בצורה מכוונת),
    במיוחד היום עם מנגנוני ה-data-binding שינוי קטן יכול להוביל לשרשרת של שינויים שצריך לקחת בחשבון.

  2. עדי

    12 ביולי 2017 - 8:12
    תגובה

    מתי תעלו את שני העקורנות שנשארו?

השאר/י תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *