Aspect Oriented+ אובייקטים מרוחקים

gewitter

New member
Aspect Oriented+ אובייקטים מרוחקים

שתי שאלות ששאלתי בפורום .net בעלות אופי ארכיטקטורליסטי יותר מאשר טכנולוגי. אני מאמין שמי שיש לו קצת נסיון יבין את הבעיה, גם אם התייחסתי ל-.net. האחת שואלת על אובייקטים מרוחקים, ועל יבוא הגדרת class משרת מרוחק, כפיתרון לאי-תלות במימוש: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=75460297 השניה עוסקת ב Aspect Oriented Programming ובשימוש ב- Attributes, בעיקר בהקשר ל-persistence, כי זה מה שאני מנסה לתקל בזמן האחרון: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=75911294 תודה לעונים, וחג שמח. אגב, קראתי את Holub on Patterns וזה ספר מצויין. עכשיו עברתי ל Patterns of Enterprise Application Architecture שגם נותן לי הרבה רעיונות, אבל לפעמים יש הרגשה שהמחבר קצת מפוזר, ויש כמה טכניקות שלא ממש אהבתי, כמו Layer Supertype שמסתמך על הורשה, ולא שאני פנאט של מימוש ולא הורשה, אבל צורת התכנות הזו מאוד מבלבלת. אולי אתן סקירה יותר מעמיקה על שני הספרים כשאהיה בטוח שאני מבין אותם לגמרי.
 

ייוניי

New member
לגבי הבעיה הראשונה

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

gewitter

New member
משרשר שאלותיי: מידול אובייקטים

האם כשאתם ממדלים את האובייקטים בתוכנה שלכם, אתם מנסים לדמות הכל לעולם האמיתי, או שאתם מגבילים את עצמכם לקונטקסט של תוכנה? מה שאני מנסה לברר זה האם באמת צריך לשקף את המציאות באובייקטים, או רק לדאוג שהאובייקט עם המידע יהיה גם זה עם ההתנהגות. כולנו מכירים את הדוגמה של מכללה. יש סטודנטים, מרצים, קורסים, כיתות וכו'. האם כשאני רוצה לראות רשימת קורסים של סטודנט מסויים, אני מבקש מאובייקט הסטודנט את הרשימה? אם בנוסף אני רוצה לראות רשימת קורסים בסמסטר מסויים, האם אני מבקש מאובייקט הסמסטר את הרשימה? בחיים האמיתיים אני לא יכול לבקש מסמסטר שום דבר. האם אני צריך להמציא בשביל זה משהו כמו CoursesRepository או CoursesManager בעל המתודה get_courses_for_student וגם get_courses_for_semester? האם כשאני רוצה לדעת כמה תרוויח האוניברסיטה השנה, אני רץ על רשימת סטודנטים ומבקש מהם את הסכום שהם צריכים לשלם? האם יש לי אובייקט פיננסי נפרד עם מתודה calculate_payment_for_student? האם כשאני רוצה לרשום סטודנט לאוניברסיטה, אני צריך לקרוא למתודה של מזכירה עם פרטי הסטודנט בפרמטרים, והיא בתורה תקרא למתודה של אובייקט מאגר_סטודנטים שאחראי לשמור על המידע? אולי אסור לי למען הבהירות לקרוא למחלקה שלי "סטודנט", אלא חשבון_סטודנט, כי הסטודנט האמיתי בכלל לא שייך לתוכנה. דוגמת החברה: יש עובדים, יש מנהלים, יש תקציב וכו' וכו'. האם כשרוצים להוציא תלוש שכר, קוראים ל- employee.GetSalary? האם קוראים ל- company.CreatePayChequeFor(employee) ואולי אובייקט זה בתורו קורא לאובייקט הנהלת_חשבונות? דוגמת החנות: אם יש לקוח שאני רוצה לחייב, אבל לקוחות מקבלים הנחה לפי סכום הקניה, מספר הקניות בחודש הנוכחי, מספר הקניות הממוצע שלהם בחודש, איזור מגוריהם, אם הם קנו את הספר הארי פוטר 5 מיד יורדים 14 שקלים מהסכום הכולל, אם בכל הזמנה יש ספר אחד לפחות הם מקבלים את המשלוח הנכחי חינם, וגם יש להתחשב ב gift certificates. האם אני קורא ל- הזמנה.חשב_סכום או קורא לאיזו מתודת אלוהים שמקבלת את כל המידע ומחליטה כמה עולה ההזמנה? כבר אמרו לי שבשיטה זו החוק העסקי נמצא במקום אחד והתחזוקה קלה יותר. איך אתם הייתם עושים את זה?
 

gewitter

New member
Object-Oriented Design Heuristics

איזה כיף למצוא ספר שמקדיש פרקים שלמים בדיוק לשאלות שהעלתי אתמול בעצבים רופפים: Object-Oriented Design Heuristics. פרק 3 של הספר דן בדיוק בנושא הזה: God classes, Proliferation of classes. כללי אצבע חשובים מהפרקים האלה: Heuristic 3.2 Do not create god classes/objects in your system. Be very suspicious of a class whose name contains Driver, Manager, System, or Subsystem. Heuristic 3.3 Beware of classes that have many accessor methods defined in their public interface. Having many implies that related data and behavior are not being kept in one place. Heuristic 3.6 Model the real world whenever possible. (This heuristic is often violated for reasons of system intelligence distribution, avoidance of god classes, and the keeping of related data and behavior in one place.) הסוגריים בנקודה האחרונה מאוד מאוד חשובים ונוגעים לשאלה שלי. נקודה זו מלווה ב- Heuristic 3.7 Eliminate irrelevant classes from your design. Heuristic 3.8 Eliminate classes that are outside the system. הסופר תומך בפיזור האינטיליגנציה, ולא רק המידע, בין המחלקות (שזה בעצם א-ב של OO, אבל ראשי פרויקטים רבים לא יודעים זאת). אני מקווה שלא הפרתי זכויות יוצרים, ומאמין שהספר צריך להכנס לספרי החובה לכל מעצב. אני גם מקווה לראות כאן תגובות השאלה האחרונה שלי בהודעה הקודמת, של מה כדאי לעשות כשהחישוב תלוי בכמה אובייקטים, ולאו-דווקא בקשר של אחוז על אחוז על אחוז, אלא גם ערכים אבסולוטים, כשהסדר של החישוב משתנה ממקרה למקרה וכו'. אולי אין ברירה אלא לכתוב manager. אולי כדאי לי לכתוב פסאדו-קוד פרוצדורלי, ולבדוק אם אני יכול לבצע עליו refactoring, וזה יניב את האובייקטים הרצויים לפיתרון המתאימים בדיוק למצב שלי?
 

ייוניי

New member
מידול אובייקטים

לא מאמין במידול אובייקטים לפי "אובייקטים" שקיימים במציאות. אם תוכל להסביר לי את היתרון שבכך אני אשמח להשכיל. אני מאמין במידול אובייקטים בעזרת תהליכים (use cases). כשאני צריך לעצב תהליך מסויים אני בודק מהי התוצאה המועילה של התהליך וכיצד אני מגיע אליה תוך שימוש בקוד קיים ובקוד חדש במקרה הצורך. "האם כשאני רוצה לראות רשימת קורסים של סטודנט מסויים, אני מבקש מאובייקט הסטודנט את הרשימה?" בוא ניקח את הדוגמא הזו לרגע, ועל מנת להפוך אותה לתהליך מועיל שלם נגיד שנתוני הקורסים של הסטודנט נשמרים בקובץ XML ששמו זהה לשם הסטודנט. נגיד גם שהמשתמש גולש לכתובת מסויימת ומקבל מסך שמאפשר להזין שם סטודנט ולקבל את רשימת הקורסים בטבלה. אז אתה יוצר מחלקה וקורא לה ShowStudentCourses שיש לה מתודת הפעלה שמחזירה את המסך המקדים, מאזינה להזנה של המשתמש ובהתאם פונה לקובץ ה XML המתאים ומציגה את הנתונים שבו בטבלה. עד כאן לא הייתי צריך אובייקט "סטודנט" וגם לא אובייקט "קורס". עכשיו אני רוצה גם לאפשר הצגה של קורסים לפי סמסטר. ונניח שאופן שליפת והצגת המידע זהה ל ShowStudentCourses. אז אני "מוציא" מתוך ShowStudentCourses את כל הקוד שאוכל לעשות בו שימוש חוזר בתהליך זה לקבלת מחלקה CoursesPresenter (או משהו בסגנון) שתתאים לביצוע שני התהליכים. כמובן שזהו מקרה פשטני מאוד אבל למצבים המורכבים יותר נועדו Design Patterns ושיטות שונות אחרות. שים לב שלא יצרתי כאן מחלקות מיותרות ולכל מחלקה יש יעוד אחד ברור ולא יותר. לצורך ביצוע חישובים שכוללים מספר רב של אובייקטים אפשר להשתמש ב DPs כמו: Command, Visitor, Iterator וכו'...
 

gewitter

New member
אני דווקא מדבר על המצבים המורכבים

כמובן שסתם מערכת וובית קטנה שמציגה מידע לפי שני חיתוכים לא צריכה יותר מדי אובייקטים. מה קורה כשסטודנט נרשם לקורס עם דרישות קדם? מה קורה כשצריך לחייב סטודנט לפי מספר הקורסים שהוא לקח? אם לסטודנט גם יש כתובת מגורים על פיה אני בודק זכאות למעונות? CoursesPresenter לדעתי היא רק מחלקת מימוש, כלומר לא חלק מהאבסטרקציות שעוזרות לי לתפוס לוגיקה שהולכת ומסתבכת. למעשה זה כמעט נשמע כמו דף ווב דינאמי שמקבל ב-query string לפי מה להציג ואת הפרמטרים המתאימים. שים לב שעבור כל חיתוך שונה שאני רוצה להכניס למערכת, צריך לכתוב מתודה חדשה שחוזרת על הרבה פעולות שכבר כתבנו במתודות הקיימות (החיתוכים שחשבנו עליהם קודם). עוד יותר נוראי שכל מתודה כזו מושכת מידע על הקורס ומציגה אותו (ואני אומר את זה רק כי אנשים נוספים חוץ ממך קוראים את הדיון הזה (-; ) אני בכוונה מנסה לתקל מצבים מסובכים יותר, שעולים בדרישות שתי שניות אחרי שסיימת לכתוב קוד פרוצדורלי במהירות. אחרי זה תוהים למה זה לוקח יותר מחמש דקות להוסיף את השינוי...
 

ייוניי

New member
-->

מה שניסיתי להראות זה שכשאתה ניגש להתמודד עם use case אתה בוחן מהו הדבר החדש שאתה צריך להוסיף ומהם הדברים הקיימים שאתה יכול להשתלב אליהם מבלי להוסיף. בדרך כלל אצלי האבטרקציות מתגלות בתהליך ולא נקבעות מראש. כל מחלקה היא מחלקת מימוש והיא צריכה לממש משהו. הכוונה שלי הייתה ש CoursesPresenter מקבל פרמטרים בסגנון: "תאור במסך של שדה החיתוך", "מיקום קבצי ה XML" ו-"url להאזין לו להצגת המסך". כל שכל חיתוך חדש (בסגנון הזה) הוא שורת קוד (שיוצרת אובייקט CoursePresenter מתאים) - וזה המצב האופטימאלי. יתרה מזאת אין כאן משיכה של מידע. המידע נקרא מקובץ ה XML ונכתב ל HTML אבל אין כאן אובייקטים נוספים במערכת ש"מבקשים" את המידע בשום צורה. אבל אני אנסה לתקוף מכוון אחר ולהראות לך מחלקה שהיא ברמה גבוהה יותר בסולם האבטרקציה. לשאלתך "מה קורה כשסטודנט נרשם לקורס עם דרישות קדם?"
interface CoursesCompletedByStudent { bool contains(int courseId); } interface ShouldStudentBeAllowedToEnrollCourse { void yes(); void no(); } class RequirementsCheck { public static void check(CoursesCompletedByStudent coursesCompletedByStudent, ShouldStudentBeAllowedToEnrollCourse shouldStudentBeAllowedToEnrollCourse, int[] _prequisiteCourseNumbers) { //.. implementation } }​
אז יש לך כאן (למעשה אין אבל תשלים לבד) קוד קצת יותר אבסטרקטי שמבצע פעולה מסויימת. הסיבה שאני לא אתחיל ממנו (בד"כ) היא שמנסיוני רוב המתודות הסטטיות שלי מוצאות דרכן לתוך מחלקות כמתודות פרטיות. אבל בכל זאת יש לך כאן אפשרות לעשות שימוש חוזר אינסופי במתודה הזו כי היא איננה תלויה במימוש. עדיין לא כתבתי כאן מחלקה ששמה "סטודנט" וזאת מפני שבהקשר הנוכחי אני לא זקוק לה. יש סיכוי שבסופו של דבר תזדקק לה אבל זו נקודת סיום לא התחלה.
 

gewitter

New member
חצי ../images/Emo45.gif

אהבתי נורא, חוץ מהממשקים המצומצמים (לקחת את זה לקיצוניות בצד השני הפעם). אני עדיין חושב שאובייקט קורס (או לפחות ממשק "קורס") יבדוק אם אובייקט סטודנט מורשה להרשם אליו. לא ממש צריך ממשק נפרד לבדיקות. מה שאהבתי זה שבמקום להחזיר רשימת קורסים אליהם הוא רשום, סטודנט רק אומר אם הוא רשום לקורס מסויים. המתודה הסטאטית check לעומת זאת היא נוראית לדעתי. אני לא רוצה לקבל שק של קורסים שאני יכול לבדוק, וקורסי חובה, ולערבב אותם יחד. אני רוצה לשאול את שק הקורסים ישירות אם יש בו את כל הקורסים בשק אחר, נניח. במקרה הזה יש שני שקי קורסים. אחד באובייקט קורס (השק מכיל את קורסי הקדם), ואחד בסטודנט, המכיל את הקורסים שהוא סיים. גם כאן אני לא צריך עוד אובייקט או ממשק עם מתודה שלוקחת את שניהם בתור פרמטרים. אני פשוט אומר:
bool canStudentAssign = courseObj.CanAssign(studentObj)​
אין מערכים, אין int-ים. עכשיו הסטודנט יכול לחשוף ממשק של ערימת קורסים שהוא סיים. יתכן שהערימה תוכל להציג את עצמה. ואני רוצה לחזור לשאלת החיתוכים המרובים: אם עכשיו אני רוצה קורסים הפתוחים בסמסטר מסוים, קורסים שמשקלם 5 נק' אקדמאיות, קורסים שמרצה מסוים מלמד וכו' וכו' וכו'. הצעתך היא לממש Presenter עבור כל חיתוך? כל אחד כזה ידע בדיוק איזה מידע הוא צריך ומאיפה למשוך אותו ואיך לערבב הכל יחד? ואז בכל Presenter יש קוד low level כמעט לגמרי, שיודע להתחבר ל-DB (אפילו דרך אובייקטי עזר) או לקרוא XML. זה פיתרון מאוד יעיל, אבל אפילו את בקשת הרשימה אני רוצה לבצע על אובייקטי דומיין. הקטע המעצבן בשבילי הוא החיתוך לפי מספר גורמים. רשימת כל הקורסים בסמסטר מסוים שמלמד מרצה מסוים ואולי שהם גם בעלי משקל של 5 נק' אקדמאיות.
 

ייוניי

New member
-->

לגבי ממשקים מצומצמים אני מציע לך לקרוא על עקרון שנקרא Interface Segregation Principle - תמיד להעדיף לשלוח את הממשק המינימלי ביותר על מנת להוריד צמידות. לגבי המתודה זו בדיוק הסיבה שאני לעולם לא אתחיל בלכתוב את המתודה הזו. הקוד שבה יהיה קיים בסופו של דבר במקום כלשהו במערכת אבל זה תלוי במימוש - אולי כחלק מ"קורס" אולי כחלק מ"סטודנט" ואולי באף אחד מהם. בסופו של דבר תהיה השוואה של int-ים כי בסוף תמיד יש התכנסות ל low level אבל אני מסכים איתך שיש המון דרכים לבצע את הבדיקה הזו ואין דרך אחת נכונה לכל מערכת. לגבי החיתוכים המרובים הנה רעיון:
interface RangedRowSet { void initialize(ValuesUserCanSetBeforeRowsAreFetched values); void fetchRows(RangedFetchValues values, System.IO.TextWriter htmlOutput); } interface ValuesUserCanSetBeforeRowsAreFetched { void addTextValue(string name); void addNumericValue(string name); } interface RangedFetchValues { long getNumericValue(string name); string getTextValue(string name); } class CoursesRangedRowSetImpl : RangedRowSet { public void initialize(ValuesUserCanSetBeforeRowsAreFetched values) { values.addTextValue("שם סטודנט"); values.addNumericValue("משקל ביחידות אקדמאיות"); } public void fetchRows(RangedFetchValues values, System.IO.TextWriter htmlOutput) { fetchRows(values.getTextValue("שם סטודנט"), values.getNumericValue("משקל ביחידות אקדמאיות"), htmlOutput); } void fetchRows(string studentName, int coursePoints, System.IO.TextWriter htmlOutput) { //.. הנקודה ברורה } }​
אני מזכיר שזו רק דוגמא לצורך המחשה, אפשר לראות איך אני יכול לחסוך בהמשך קוד בעזרת משפטי SQL דינאמיים או לחילופין לבצע את השליפה בעזרת תקשורת עם אובייקטי "דומיין".
 
למעלה