עזרה ב Refactoring של AutoFill

gewitter

New member
עזרה ב Refactoring של AutoFill

שלום, אני עובד על תיבת טקסט שמשלימה את הטקסט לפי הקלדות המשתמש. זה עבד מזמן, אבל אני משתגע עם חלוקת התפקידים של האובייקטים. נסיתי כל מיני גישות, ולפעמים פתחתי (inline) כל מיני אנקפסולציות שחשבתי שצריך קודם לכן, כדי לפשט את הקוד. מדובר ב Java Swing. יש ב-swing צמידות גבוהה בין רכיב ה-gui למודל שלו, וזה כאילו שניהם אובייקט אחד, מה שמקשה על העניינים מבחינה טכנית. הבעיה גם שהקלט מגיע מאותו רכיב gui שאותו משנים כדי להראות השלמה אוטומאטית (זה הפיצ'ר בעצם, אבל זה מקשה על העניינים, כמובן), וצריך לשמור על מצב התצוגה בכל מיני מקומות. האם מישהו מקהל אוהבי ה refactoring והחלוקה לממשקים יעזור בנושא?
 

gewitter

New member
צמצמתי:טיפול ב-if מקונן לפי מצב

הכתורות הקצרות בתפוז כמעט מיאשות :) אני מצמצם את השאלה והתמקדות ה-refactoring לקטע מתוך מחלקה אחת. זוהי מחלקה המטפלת בקלט מהמשתמש, ומתיחסת עליו בצורות שונות, לפי עיבודים קודמים ואירועים חיצוניים. יש לי למעשה if-ים מקוננים כדי להגיע למצב הנוכחי, למרות שזה לא נראה כך כי כל if נמצא במתודה נפרדת. נסיתי להשתמש ב State Pattern אבל נראה שזה הוסיף הרבה יותר קוד ממה שרציתי. עדיין יתכן שאם אתקן את חשיפת המצב לגורמים חיצוניים, אוכל לשנות את ההתנהגות "במכה אחת" בהתאם לאירוע החיצוני. (כרגע יש כמה שדות public שהכנסתי כי לא ידעתי באותו רגע איך לאפשר שינוי נקי בהתנהגות) יש מצב שמשנה את ההתנהגות לגמרי, ויש כזה שמשנה קצת את הקלט ומעביר אותו הלאה. אני לא יודע אם צריך להתיחס אליהם אחרת. המצב שמתחלף בעקבות אירועים חיצוניים ראוי לטיפול משלו, אבל לא אלאה עם זה כרגע. הנה החלק החשוב של הקוד. אחריו מודבקת המחלקה כולה, אם מישהו מרגיש צורך בפרטים. מתודות תלויות מצב הן: insertString, remove, replace ואלו שנקראות מתוכן.
public void replace(int offset, int length, String sub) { replacing = true; delegator.replace(offset, length, sub); } public void insertString(int offset, String addition) { replacing = false; TextFieldOutputFactory.TextFieldOutput output = factory.createOutput(delegator, new DefaultInsert(offset, addition)); insertString(offset, addition, output); autoFilledState = output.autoFilled(); } public void insertString(int offset, String addition, Output output) { String proposedValue = new StringBuilder(getText()).insert(offset, addition).toString(); matcher.match(proposedValue, output); } public void remove(int offset, int length) { removeMethodSelector(offset, length); replacing = false; } private void removeMethodSelector(int offset, int length) { if (replacing) { delegator.remove(offset, length); } else { removeDependOnState(offset, length); } } private void removeDependOnState(int offset, int length) { if (backspacing && autoFilledState) { offset--; length++; } TextFieldOutputFactory.TextFieldOutput output = factory.createOutput(delegator, new DefaultRemove(offset, length)); remove(offset, length, output); autoFilledState = output.autoFilled(); } public void remove(int offset, int length, Output output) { String proposedValue = new StringBuilder(getText()).delete(offset, offset + length).toString(); matcher.match(proposedValue, output); }​
והמחלקה כולה:
public class AutoFillPlugin implements yon.ui.autofill.PluggablePlainDocument.Plugin { public void acceptDelegator(yon.ui.autofill.PluggablePlainDocument.Delegator delegator) { this.delegator = delegator; } public void replace(int offset, int length, String sub) { replacing = true; delegator.replace(offset, length, sub); } public void insertString(int offset, String addition) { replacing = false; TextFieldOutputFactory.TextFieldOutput output = factory.createOutput(delegator, new DefaultInsert(offset, addition)); insertString(offset, addition, output); autoFilledState = output.autoFilled(); } public void insertString(int offset, String addition, Output output) { String proposedValue = new StringBuilder(getText()).insert(offset, addition).toString(); matcher.match(proposedValue, output); } public void remove(int offset, int length) { removeMethodSelector(offset, length); replacing = false; } private void removeMethodSelector(int offset, int length) { if (replacing) { delegator.remove(offset, length); } else { removeDependOnState(offset, length); } } private void removeDependOnState(int offset, int length) { if (backspacing && autoFilledState) { offset--; length++; } TextFieldOutputFactory.TextFieldOutput output = factory.createOutput(delegator, new DefaultRemove(offset, length)); remove(offset, length, output); autoFilledState = output.autoFilled(); } &nb​
 

gewitter

New member
מצמצם יותר: טיפול בתלות במחלקת האם

במצב כמו זה, שאין ברירה נורמאלית אלא לרשת ממחלקה קיימת, אני תלוי לגמרי במימוש של מחלקת האם, שאני במקרה מודע אליו מבדיקות וקריאת קוד ה-JDK. כדי להוסיף פונקציונליות לפני שינוי תוכן שדה הטקסט, צריך לרשת ממחלקה שמייצגת את התוכן, ולדרוס את המתודות המשנות את התוכן. אני דורס את insertString, remove ו- replace. המתודה replace גורמת לתלות כי היא קוראת בעצמה ל-remove ואז ל-insertString. אני מעוניין בכל זאת להשתמש ב super.remove (או base.Remove אם זה היה C#) כי היא מממשת נעילה. אז אני רוצה לחכות שהיא תקרא ל-remove, אבל לא לבצע פעולות יקרות שם, כי מיד אח"כ היא תקרא ל insertString, שם אבצע השלמה אוטומאטית של הטקסט. בגלל זה אני מחזיק משתנה בולאני שנקרא replacing. זה לא הדבר הכי נורא בעולם, אבל אני גם מממש התנהגות נוספת שתלויה במצב שאינו קשור לזה: כאשר הטקסט הושלם אוטומאטית והמשתמש לחץ על Backspace. איך אתם הייתם מטפלים בזה? כבר נסיתי State Pattern וזה לא עלה יפה. אולי לא הלכתי עם זה עד הסוף. אז חשבתי על Decorator: יש לי ממשק שנקרא Plugin שמימושיו יודעים להגיב להכנסה, הסרה והחלפה של טקסט. אני יכול לבנות שרשרת של Plugin-ים, כל אחד עוטף את קודמו, והאחרון, זה שעוטף את כולם, מתחבר ישירות לרכיב הטקסט. הרכיב הפנימי ביותר יהיה נאיבי ויגיב בדיוק להוראות הניתנות לו, ויממש השלמה אוטומאטית של הטקסט. זה שמעליו יטפל ב Backspace המיוחד. זה שמעליו יטפל ב replace הסורר. אני בעצם מפריד שרשרת המתודות שכרגע יש לי ב Plugin אחד, כל מתודה בPlugin אחר, ואז יש לי גמישות לגבי איזו התנהגות להוסיף או להוריד (נניח ש Sun תשנה את המימוש של replace, אני מוריד את ה ReplacePlugin מהשרשרת :)). אגב, אני לא מנסה בכוח להשתמש ב design patterns, פשוט דמיינתי מין ערימה של שקעים/מתאמים חשמליים מוזרים שמתלבשים אחד על השני וכל אחד מסנן איכשהו את הזרם. זה נשמע לי כמו Decorator, אז אל תהרגו אותי רק כי הזכרתי את זה.
 

gewitter

New member
אף יותר פשוט: מחלקה אחת שעושה הכל

הנה המחלקה, עם כל הקוד שלה פלוס הערות. אין התחכמויות, אין אובייקטים, אין patterns. קצת יותר ממאה וקצת שורות. איך הייתם משפרים את הקוד? החל מחלוקה למתודות קטנות יותר וכלה ביצירת מחלקות חדשות. הכל מאוד דומה ל-C#. הקפדתי על זה בכוונה. חוץ מ Event Handlers שנראים מוזר. כתבתי הערה איפה שצריך. בחלק מהמקרים מתודות חייבות להצהיר על ה Exceptions שהן זורקות, אבל זה לא נורא. boolean זה כמו bool JTextField זה כמו TextBox. extends זה : הקוד בקובץ טקסט המצורף.
 
למעלה