שאלת Design

gmorphus

New member
שאלת Design ../images/Emo132.gif

אני מכריז על הבעיה שלי בעיית Design אולם היא קשורה במידה מסוימת לזה שאני מפתח את המערכת הזו ב C#. הבעיה היא כזו: קיימת איזושהי תשתית של מחלקות שמקומפלת ל Lib. כאשר אני בא לכתוב את המערכת אני יורש מכל מיני מחלקות, מממש כל מיני ממשקים ומקשר הכל ביחד עם ה lib. יש לי מחלקה בשם BaseServiceProvider והיא מממשת את IServiceProvider (יורשת ממנו). מחלקה נוספת בשם BaseClient שיודע וצריך לדבר עם IServiceProvider. אני יוצר מחלקה חדשה DerivedServoceProvider שיורשת מ BaseServiceProvider. בנוסף אני כמובן יוצר DerivedClient שיורש מ BaseClient. ה DerivedClient שלי צריך לדעת לדבר עם ה ServiceProvider שלו וזאת כמובן אך ורק דרך המממשק. מכיוון ש DerivedServoceProvider מרחיב את המחלקה ממנה הוא יורש הממשק הבסיס IServiceProvider אינו מספיק, וה DerivedClient שלי צריך לדעת לדבר עם ממשק אחר. נ.ב. ה Client (בין אם הבסיס ובין אם היורש) מקבל מצביע (או משהו כזה) לServiceProvider, אוחם הוא ניגש אליו רק דרך הממשק. אני מעוניין ליצור תשתית, כך שכל מי שרוצה יוכל ליצור מערכת משלו על בסיס התשתית שלי ואני צריך לאפשר את היכולות שתיארתי לעיל. הנה כמה בעיות שניתקלתי בהן: א. תמיד אפשר ש DerivedClient פשוט יעשה casting מהמצביע אותו הוא מקבל ל DerivedServiceProvider. זה יעבוד טוב אבל לא יהיה נכון מבחינת Design. אני רוצה לשמור על עניין הממשקים. זה חשוב להמשך... ב. אני לא הצלחתי לחשוב על דרך שבה אני יכול ליצור ממשק IDerivedServiceProvider שיורש מIServiceProvider כי אז DerivedSericeProvider יצטרך לרשת גם ממנו וגם BaseServiceProvider שיורש IServiceProvider, ותיווצר פה ירושה מעגלית... אני מקווה שאני מספיק ברור. אם לא תגידו מה לא ברור לכם ואני אבהיר את הנקודה. ומי שהגיע עד לפה אני מודה לו בכל מקרה
 

vinney

Well-known member
קודם כל תגיד תודה ../images/Emo13.gif

לא כל כך הבנתי את הבעיה. אתה יוצר מחלקה A ומחלקה B שיודעות לדבר אחת עם השניה, אז אתה מרחיב את A עם AA שיורשת ממנה, יורש מB איזהשהי BB, ורוצה שBB ידע לדבר עם ההרכבות של AA, זה הבעיה? תפשט קצת, ואם אפשר דוגמאת קוד - זה יעזור. לדעתי (אם היית עובד ב++C), זה בדיוק המקום למחלקות וירטואליות, אני לא יודע מה המנגנון של #C שעושה את זה, אבל אם תדגים למה התכוונת, אבין יותר טוב
 

gmorph

New member
אמרתי בכל ../images/Emo51.gif!

אין קוד. זו שאלת design בשנשאלת בדיוק מתי שהיא אמורה להשאל - בשלב האפיון. לא יכולתי לפשט הרבה יותר אבל אני אנסה. מחלקה A וB לא בדיוק יודעות לדבר אחת עם השנייה. מחלקה A (נניח, נותנת השירות) מייצאת איזשהו ממשק IA ולמחלקה B. ומחלקה B מחזיקה נניח איזשהו מצביע (או משהו כזה) לאותו ממשק IA של מחלקה A. A, עצם היותה מייצאת את ממשק IA כמובן יורשת ממנו וממשת את הפונקציות שנמצאות בו. עד כאן הכל טוב ויפה - אבל חשוב לציין שגם A, IA וB הן חלק מתשתית (עתידית כמובן) אני רוצה לכתוב מערכת שמשתמשת בתשתית הנ"ל. זאת אומרת אני רוצה להרחיב את התשתית. כלומר, להרחיב את נותן השירות A ולרשת ממנו DA (האות D על שום Derived), וגם להרחיב את B ולרשת ממנה DB. הבעיה נוצרת כאשר DB רוצה להשתמש בנותן השירות שלה DA. אני לא מעוניין שהיא תשתמש בו ישירות אלא בממשק כלשהו IDA. השאלה היא איך אני עושה את זה? פונקציות וירטואליות לא יעזרו כאן (וכמובן שהמנגנון קיים גם בC#), למה אתה חושב שזה יעזור לי?
 

vinney

Well-known member
לא ברור לי המושג "ממשק"

אני לא תכנתתי ב#C כבר הרבה זמן, אבל אני לא זוכר שקיים מושג כזה שם... תוכל לתרגם ל++Cית?
 

vinney

Well-known member
זה לא מה שהבנתי מההסבר

הוא אומר שמחלקה A מייצאת ממשק IA, במקרה של pure virtual הייתי אומר שממשק IA ממומש על ידי מחלקה A... gmorphus, תוכל להסביר?
 

gmorph

New member
אוקיי

ממשק הכוונה בסה"כ למחלקה שמכילה אך ורק פונקציות pure virtual. הממשק IA הוא מחלקה שמכיל פונקציות כאלה, וכשאני אומר שהמחלקה A מממשת את הממשק IA אני מתכוון שהיא יורשת מIA ומממשת את הפונקציות הוירטואליות שלו. B עובדת עם מצביע לIA שבפועל הוא מצביע לאובייקט כלשהו שיורש מIA. זה יכול להיות A אבל זה גם יכול להיות אובייקט מטיפוס DA שיורש מA שיורש מIA... עכשיו המחלקה DB אמורה להיות מסוגלת לדבר עם DA דרך ממשק, השאלה היא דרך איזה ממשק? דרך IA אני לא יכול לגשת לפונקציות הנוספות שהוספתי בDA, ואני גם לא יכול להמציא ממשק חדש IDA שיירש מIA ואז להגיד לDA לרשת מIDA, כי היא גם חייבת לרשת מA עצמה... ואז תהיה מעין ירושת יהלום. אוי אני מתחיל להתבלבל בעצמי
קפיש?
 

vinney

Well-known member
למה ירושת יהלום?

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

gmorphus

New member
אני לא יכול לשים את התוספות של IDA

בIA המקורי, מהסיבה הפשוטה שאני לא יודע מהן יהיו! המחלקות A ו-B מהוות תשתית, והמחלקות DA, DB הן הרחבה לתשתית, אבל זו הרחבה שלא בהכרח ידועה מראש. הרעיון הוא שאני יכול להרים מערכת חדשה שמשתמשת בתשתית שלי בכל זמן נתון בלי לדעת את זה מראש כמובן... למה ירושת יהלום? כי אם A יורש מIA וDA יורש מA שיורש מIA הוא לא יכול לרשת גם מIDA כלשהו שיורש מIA. IDA, אם אני בוחר להשתמש בממשק כזה לא צריך להכיר את המימוש עצמו. המימוש יהיה בDA שיירש מA... בקיצור יש פה בלאגן. אני רוצה שמחלקות שונות ידברו ביניהן עם ממשקים מצד אחד, מצד שני אני רוצה לשמור על היררכיית מחלקות מסוימת.
 

vinney

Well-known member
תבין משהו

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

gmorphus

New member
כל מה שאמרת הוא נכון

ועם זאת אני בבעיה... האמת שנגעת ממש בנקודה. צירפתי סרטוט: יש את מחלקות הבסיס. מחלקה B שמדברת עם A דרך ממשק IA. אני רוצה להרחיב גם את A וגם את B באמצעות DA ו DB בהתאמה. אני רוצה שהמחלקה DB שכמובן מודעת לתוספות החדשות בDA תדע לדבר עם DA דרך ממשק - נקרא לו X. מכיוון שאני רוצה ש DB תדבר עם DA דרך X אז DA חייבת לרשת מX לא? אבל מחלקה DA יורשת באופן טבעי גם מA (כי מרחיבה את הפונקציונליות שלה). זה נותן לDA את הממשק IA שדרוש לB (שכן לB יש עדיין פונקציות שצריכות את הממשק IA קיים עבורן). אבל איך אני מכניס עוד ממשק לתמונה?
 

gmorphus

New member
כמה הערות לגבי הסירטוט

עיגול הוא מחלקה אבסטרקטית - או במילים אחרות ממשק. מלבן הוא מחלקה "אמיתית" בעלת מימוש. קו עם יהלום בקצה אומר מחלקה שמממשת את הממשק שמחובר ליהלום. חץ מקווקו אומר שמחלקה במוצא של החץ "מדברת" עם המחלקה בקצה החץ. חץ ריק אומר שהמחלקה במוצא החץ יורשת מהמחלקה בקצה החץ.
 

vinney

Well-known member
לא לא לא

כדי שDB תדבר עם DA היא לא צריכה לרשת שום דבר מX, היא צריכה להכיר מתודות שDA מממשת (שמוגדרות בX). היא צריכה לרשת מX אמ"מ היא מממשת את X, והיא לא עושה את זה, מי שעושה את זה זה DA. המ שקורה, בשרטוט שלך, (וסלח לי שאין לי שום דבר UMLי פה, אפילו לא ויזיו, אז אתאר) זה דבר כזה: מB מורשת מחלקה DB שמכירה (חץ מקווקו אצלך) את הממשק IDA (שכולל בתוכו את הממשק IA) עכשיו יש לך שתי אופציות: 1. מIA מורשת מחלקה אבסטרקטית IDA וממנה מורשת DA שמממשת את IDA וIA 2. מA מורשת מחלקה אבסטרקטית IDA שממנה מורשת DA שמממשת את IDA (וIA ממומש כבר במסגרת A). בשני המקרים הIDA שנוצר יתאים למה שמכירה DB
 

gmorphus

New member
מתי אמרתי שDB צריכה לרשת מX

נראה לי שלא הבנת אותי נכון בנקודה הזאת, אבל ניחא... ברור שמי שאמור לרשת מX היא אם כבר DA. גם לי אין שום דבר UMLי - זה בסה"כ Word
... סוף סוף נראה לי שהבנת את הבעיה שלי כמו שצריך.
ועכשיו לגבי הפתרונות שהצעת: 1. את הראשון אני די פוסל על הסף כי למעשה אני צריך בDA לממש גם את IDA וגם את IA (תקן אותי אם אני טועה). 2. הפתרון הזה נשמע לא רע והוא כמובן יפתור את הבעיה, אבל אני לא יודע אם הוא נכון מבחינת OOD. מה פתאום ממשק יורש ממחלקה "אמיתית"?
עוד רעיונות? נ.ב. ויני המון תודה גם אם לא יהיו לך עוד רעיונות
 

vinney

Well-known member
את הראשון גם אני לא אוהב

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

gmorphus

New member
עוד רעיון - מה אתם חושבים?

1. הממשק IDA אינו יורש מIA או משום דבר אחר. הדבר היחיד שהוא עושה זה להוסיף פונקציות בנוסף לאלה הקיימות כבר בIA. עכשיו, DA יירש גם מ A וגם מ IDA ויממש את הפונקציות החדשות בIDA (שים לב שאלה לא יכולות להיות פונקציות שקיימות כבר בA (או בIA לצורך העניין). המחלקה B מכירה את IA ויודעת לעבוד איתו. המחלקה DB מכירה גם את IA (כי B מכירה אותו) ומכירה את הממשק הנוסף שהיא מחזיקה IDA. למעשה יש לנו פה שני מצביעים לאותו מקום אבל עם התייחסות שונה. 2. הרעיון השני הוא שינוי תפיסתי קל. לא להגדיר בכלל ממשק נוסף אלא להוסיף בממשק המקורי IA פונקציה וירטואלית שנקראת נניח Costume והיא תשמש את המחלקות שיורשות מB (למשל DB) לדבר עם המחלקות היורשות מA דרך אותה הפונקציה. איך זה? מה נשמע יותר טוב?
 

tseliot

New member
הרעיון הראשון הוא מה שהתכוונתי

להציע בעצמי... אגב, למה אתה מדבר כל הזמן על מצביעים? אתה בC#, לא? עוד משהו, אתה בטוח שאי אפשר ליצור ממשק IA2 שיורש את IA? ראיתי שפסלת את הרעיון הזה לפני כמה הודעות, אבל חשבתי שאולי C# תאפשר למחלקה לרשת גם ממשק שנגזר מIA וגם מחלקה שנגזרת מIA משום שבניגוד לירושה כפולה של מחלקה, הממשק לגמרי מופשט והירושה הכפולה שלו לא יוצרת כפילויות כלשהן... אני בספק אם זה ככה, אבל שווה לנסות להריץ ולראות אם הקומפיילר מסכים, לא? והאופציה השניה מכוערת, אל תשתמש בה...
 

gmorphus

New member
אני מדבר על מצביעים

כי אני C++-י מושבע
ועוד לא תכנתתי ב #C - אבל הכוונה יכולה להיות ל reference או משהו דומה. הרעיון הוא אותו רעיון. למה אני אומר שליצור IDA שיירש מIA זה לא טוב? תחשוב שיצרתי כזה ממשק (IDA) שיורש מIA. ואז אני יוצר מחלקה DA שיורשת מIDA ומממשת אותו. עד כאן טוב ויפה. אבל מה? אני חייב שלDA יהיו את היכולות של A - זאת אומרת שהיא חייבת גם לרשת מA. אבל A גם יורש מIA ואני חושש שזה עלול לעשות בעיות. זה מה שנקרא אגב ירושת יהלום.
 

gmorphus

New member
אה, והאופציה השנייה לא כל-כך גרועה

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