מדוע שיטות גטר וקובע הן מרושעות

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

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

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

על אופי העיצוב

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

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

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

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

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

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

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

הפשטת נתונים

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

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

אם X היה int, אבל עכשיו חייב להיות a long, תקבלו 1,000 שגיאות קומפילציה. אם אתה פותר את הבעיה באופן שגוי על ידי הטלת ערך ההחזרה int, הקוד יתקדר בצורה נקייה, אך הוא לא יעבוד. (ערך ההחזרה עשוי להיות קטוע.) עליך לשנות את הקוד סביב כל אחת מאותן 1,000 שיחות כדי לפצות על השינוי. אני בהחלט לא רוצה לעשות כל כך הרבה עבודה.

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

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

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

שיטות Getter ו- Setter (המכונות גם accessors) מסוכנות מאותה סיבה publicששדות מסוכנים: הם מספקים גישה חיצונית לפרטי היישום. מה אם אתה צריך לשנות את סוג השדה שהגישה אליו? עליך גם לשנות את סוג ההחזרה של הגישה. אתה משתמש בערך החזרה זה במקומות רבים, לכן עליך גם לשנות את כל הקוד הזה. אני רוצה להגביל את ההשפעות של שינוי להגדרה כיתתית אחת. אני לא רוצה שהם יפלו לתוכנית כולה.

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

היעדר שיטות getter / setter לא אומר שחלק מהנתונים לא זורמים במערכת. עם זאת, עדיף למזער ככל האפשר את תנועת הנתונים. הניסיון שלי הוא כי יכולת התחזוקה ביחס הפוך לכמות הנתונים שעוברת בין אובייקטים. אם כי אולי עדיין לא תראו איך, תוכלו למעשה לחסל את רוב תנועת הנתונים הזו.

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

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

אביזרים מסתיימים גם בעיצובים מכוח הרגל. כאשר מתכנתים פרוצדורליים מאמצים ג'אווה, הם נוטים להתחיל בבניית קוד מוכר. לשפות פרוצדורליות אין שיעורים, אך יש להן את ה- C struct(חשוב: שיעור ללא שיטות). נראה אם ​​כן טבעי לחקות א על structידי בניית הגדרות כיתתיות כמעט ללא שיטות ולמעט publicשדות. מתכנתים פרוצדורליים אלה קוראים איפשהו כי שדות צריכים להיות private, עם זאת, ולכן הם מייצרים את השדות privateומספקים publicשיטות גישה. אבל הם רק סיבכו את הגישה הציבורית. הם בוודאי לא הפכו את המערכת לאובייקט.

צייר את עצמך

השלכה אחת של אנקפסולציה מלאה בשדה היא בבניית ממשק משתמש (UI). אם אינך יכול להשתמש באביזרים, לא תוכל לקבוע getAttribute()שיטת בונה ממשק משתמש . במקום זאת, לשיעורים יש אלמנטים כמו drawYourself(...)שיטות.

getIdentity()שיטה יכולה גם לעבוד, כמובן, בתנאי שהוא מחזיר אובייקט מיישם את Identityהממשק. ממשק זה חייב לכלול שיטה drawYourself()(או תן לי- JComponentזה-מייצג-זהותך). למרות getIdentityשמתחיל ב"קבל ", זה לא אביזר מכיוון שהוא לא רק מחזיר שדה. הוא מחזיר אובייקט מורכב שיש לו התנהגות סבירה. גם כשיש לי Identityאובייקט, עדיין אין לי מושג איך זהות מיוצגת באופן פנימי.

כמובן drawYourself()שאסטרטגיה פירושה שאני (מתנשם!) מכניס קוד ממשק משתמש להיגיון העסקי. שקול מה קורה כאשר הדרישות של ממשק המשתמש משתנות. בוא נגיד שאני רוצה לייצג את התכונה בצורה אחרת לגמרי. כיום "זהות" היא שם; מחר זה שם ומספר תעודת זהות; יום אחרי זה שם, מספר תעודת זהות ותמונה. אני מגביל את היקף השינויים הללו למקום אחד בקוד. אם יש לי תן-לי- JComponentזה שמייצג-זהות שלך, אז בודדתי את האופן שבו זהויות מיוצגות משאר המערכת.

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

JavaBeans

אתה יכול להתנגד באמירה, "אבל מה עם JavaBeans?" מה לגביהם? אתה בהחלט יכול לבנות JavaBeans ללא גטרים וקובעים. BeanCustomizer, BeanInfo, ו BeanDescriptorכיתות כל קיימים בדיוק למטרה זו. מעצבי המפרט של JavaBean השליכו את אידיו הגטר / סטר לתמונה מכיוון שהם חשבו שזו תהיה דרך קלה להכין במהירות שעועית - דבר שתוכלו לעשות בזמן שאתם לומדים כיצד לעשות זאת נכון. לרוע המזל איש לא עשה זאת.

אקססוריז נוצרו אך ורק כדרך לתייג מאפיינים מסוימים כך שתוכנית בונה ממשק משתמש או מקבילה יכולה לזהות אותם. אינך אמור להתקשר לשיטות אלה בעצמך. הם קיימים לשימוש אוטומטי בכלי. כלי זה משתמש בממשקי ה- API של התבוננות פנימית Classבכיתה כדי למצוא את השיטות ולהקצין את קיומם של מאפיינים מסוימים משמות השיטות. בפועל, השפה המבוססת על התבוננות פנימית זו לא הצליחה. זה הפך את הקוד למסובך מדי ופרוצדורלי. מתכנתים שאינם מבינים הפשטת נתונים קוראים למעשה לגורשים, וכתוצאה מכך הקוד פחות מתחזק. מסיבה זו, תכונה של מטה-נתונים תשולב בג'אווה 1.5 (אמורה להגיע באמצע 2004). אז במקום:

קניין פרטי פרטי; public int getProperty () {להחזיר נכס; } setProperty public void (int value} {property = value;}

תוכל להשתמש במשהו כמו:

פרטי @ נכס פרטי; 

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

מתי אביזר בסדר?

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