Saturday, July 23, 2005

שלום לכולם,

באחד מהגיחות שלי לפורום של “מתכנתים-עם-יותר-מדי-זמן-פנוי” (או בשמו האמיתי: הנדסת תוכנה בתפוז) יצא לי להיתקל בשאלה הבאה:


איך הייתם ניגשים לממש שאלה כזו מבחינת האפיון וגם תכנותית (.NET)?

1. שתי ישויות - CONTACT , CONTACT-GROUP  (לטובת EMAIL)

2. CONTACT-GROUP יכולה להכיל גם CONTACT וגם CONTACT-GROUP

3. CONTACT יכול להיות במספר CONTACT-GROUPS או באף אחת

4. יש לאפין ולכתוב תכנה המאפשרת
   א. יצירת CONTACT  ו CONTACT-GROUP
   ב. הוספת CONTACT ל CONTACT-GROUP
   ג. מחיקת CONTACT מ CONTACT-GROUP
   ד. הדפסת כל ה CONTACTS בתצורה עצית
   ה. מיון CONTACTS עפ"י שם


 

חלק א': תיאוריה, OODA

ברגע שקראתי את השאלה הזאת עלתה במוחי התשובה הרגילה והסטנדרטית: “אההה... OODA עם שלושה שכבות? נבנה את הכל מובנה יפה והוא ירוץ סבבי-בבי”. אז הנה איך אני הייתי ממש את השאלה הזאת לפי OODA.

1. פקד בסיס אבסטרקטי - המינימום שמשותף ל-Contact ו-Contact Group (להלן: CG) הוא שלשניהם יש שם תצוגה כלשהו. ולכן, ניצור פקד אבסטרקטי כלשהו שממנו שניהם יירשו. שימו לב, הכוח האמיתי של יצירת מחלקות בסיס אבסטרקטיות יתברר בהמשך התשובה. כרגע, נציין שאנחנו עובדים לפי Abstract Factory Design Pattern או בשמה המיקרוסופטי המחודש Provider.

/* Busniss Logic Layer */
 public abstract class BaseContact
 {
  // DisplayName Property
  private string _displayName = "";
  public string DisplayName
  {
   get  { return _displayName;  }
   set { _displayName = value; }
  }  
 }

שימו לב שלא כתבתי Constructor וכן איתחלתי את המשתנה הפנימי של ה-DisplayName property לערך מחרוזת ריק. אין טעם לכתוב Constructor מצב הזה שבו ברור לנו שלא ניתן לאתחל את ContactBase.

2. ניישם את Contact באמצעות ירושה מ-ContactBase. נוסיף לו איזה תכונה לשם הדגמה בכדי להבדיל אותו מ-ContactBase. בדרישות הפרוייקט מצויין שצריך לשמור בו אי-מייל, אז באמת נוסיף לו Email property. 
בנוסף לפי דרישת פרוייקט (4) סעיף א', נבנה Constructor ל-Contact שיקלוט DisplayName ו-Email.

 /* Busniss Logic Layer */
public class Contact : BaseContact
 {
  // Email Property
  private string _email;
  public string Email
  {
   get { return _email; }
   set { _email = value; }
  } 
 
  // Constructor
  public Contact(string pEmail, string pDisplayName)
  {
   _email = pEmail;
   this.DisplayName = pDisplayName;
  }
 }

3. ניישם את CG (דהלן: Contact-Group).  לפי דרישת פרוייקט (4) סעיף א', יהיה לו Contructor.

 /* Busniss Logic Layer */
 public class ContactGroup : BaseContact
 {
  
  // Constructor
  public ContactGroup(string pDisplayName)
  {   
   this.DisplayName = pDisplayName;
  }
 }

מבחינת היישום של סעיף (1) של דרישות הפרוייקט שלנו - סיימנו. להזכירכם את סעיף 1: “שתי ישויות - CONTACT , CONTACT-GROUP  (לטובת EMAIL)“. כעת נביט על דרישות פרוייקוט (2), (3) וסעיפים ב', ג' ו-ה' בדרישה (4). אנו יודעים כי: CG צריכה צריכה אפשרות להוסיף ולזרוק: CGים ו-Contactים. בנוסף, צריך שיהיה אפשר למיין את מבנה הנתונים CG לפי מפתח שמי כלשהו.

כעת נדבר נזכיר שSortedList היא בדיוק כמו ArrayList מהבחינה שנוכל להכניס לתוכה כל Object אחרי שנעשה לו Boxing, והערך המוסף שלה (ובמיוחד לתרגיל זה) הוא שניתן לסדר את ה-ArrayList לפי מפתח שנקבע. 

נסכם, עלינו להוסיף מתודות שיאפשרו (ביחס ל-CG): להוסיף Contact, להוריד Contact, להוסיף CG ולהוריד CG. היות ו-Contact ו-Contact-Group שניהם יורשים מ-BaseContact זהו מצב אופטימלי ליצור Strongly typed Collection או במקרה שלנו BaseContact type SortedList. כלומר, דבר ראשון ניצור טיפוס נתונים פנימי שלנו שכל מטרתו בחיים היא לשמור SortedList היכולה לקבל רק ContactBase ויורשיו. מדובר על תחום השווה למאמר בפני עצמו, אבל נסתפק ביישום הזה.

 /* Busniss Logic Layer */
 /// <summary>
 ///     Strongly type SortedList for BaseContact
 /// </summary>
 /// <remarks>
 ///     Not generated by myGeneration/CodeSmith, written for demonstration.
 ///     in a real Strongly-typed collection you will implement all  Members of ArrayList.
 /// </remarks>
 public class ContactItems
 {  
  private System.Collections.SortedList _contactItems = new SortedList();
  /* public Methods for dealing with Adding/Removing Contact-Groups/Contact */
  public void Add(BaseContact curContactBase)
  {
   _contactItems.Add(curContactBase.DisplayName, (object)curContactBase);
  }
 
  public void Remove(BaseContact curContactBase)
  {
   _contactItems.Remove(curContactBase.DisplayName);
  }
  // Get Count property
  public int Count
  {
   get { return _contactItems.Count; }
  }
  // Indexer
  public BaseContact this [int index]
  {
   get { return (BaseContact)_contactItems[index]; }
   set { _contactItems[index] = value; }
  }
 }

 

בנינו את ה-Strony typed collection עם כל ה-Members שהיינו צריכים לתרגיל זה: אפשר להוסיף אך ורק ContactBase או יורשיו, ניתן להוריד אך ורק ContactBase או יורשיו, ישמנו מחדש את Count וכמו כן בנינו Indexer כדי שנוכל לגשת למערך הפנימי. באמת כל מה שעשינו זה Encupsulation ל-SortedList כך שיוכל לקבל אך ורק אובייקטים מהסוג שנקבע לו ויחשוף בדיוק מספיק פונקציונליות כדי שנוכל לעבוד איתו.

ניישם ונשכתב את ContactGroup כך שתכיל מערך פנימי של ContactItems כ-Property של ContactGroup:

 /* Busniss Logic Layer */
 public class ContactGroup : BaseContact
 {
  private ContactItems contactItems;
  public ContactItems ContactItems
  {
   get { return contactItems; }
   set { contactItems = value; }
  }
 
  // Constructor
  public ContactGroup(string pDisplayName)
  {  
   this.DisplayName = pDisplayName;
  }
 }
 

נבדוק שכיסינו את הדרישות הבאות: דרישות פרוייקט (2) ו-(3), וסעיפים ב' ו-ג' בדרישת פרוייקט (4).

דרישת פרוייקט 2 מבקשת מאתנו ש-CG תוכל להכיל CG ו-Contact. היות ושני המחלקות הללו יורשות מ-ContactBase ולכל CG יש ContactItems המכיל SortedList של ContactBase אז מילאנו דרישה זו.

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

דרישת פרוייקט 4 סעיפים ב' ו-ג' מדברים על כך שניתן יהיה להוסיף או להסיר CG או Contact מרשימה CG קיימת. היות ושני מחלקות אלו יורשות מ-ContactBase ולכל CG יש ContactItems המכיל SortedList של ContactBase ואחת מהאפשרויות היא להסיר ולהוסיף ContactBase ב-SortedList מילאנו דרישות אלו.

דרישת פרוייקט 4 סעיף ה' מדברת על מיון של CG לפי שמות התצוגה של ה-ContactItems שלה. היות ו-SortedList, שהוא טיפוס המידע הפנימי של ContactItems, מתמיין אוטומטית לפי מפתח - מילאנו דרישה זו כמו כן.

4. כל מה שנשאר הוא לממש את סעיף ד' של דרישת פרוייקט (4). והוא: תצוגה במבט עץ. ניצור Presentation Layer ובתוכה כל המימוש לתצוגת נתונים אלו. המימוש הפשוט ביותר לדרישה זו הוא דרך פקד תצוגה TreeView.

נדבר מעט על המבנה של שכבת התצוגה. כל אלמנט בשכבת התצוגה צריך אפשרות להפוך אותו ל-TreeNode (ענף בתצוגת העץ שלנו). ולכן, עלינו ליצור מחלקה שאחראית לתצוגה ל-Contact ול-CG. היות ויש לה תכונה משותפת אלינו לדאוג שהן ירשו מאותה מחלקה\ממשק. בדוגמה זו ישמתי ממשק שנורש ע“י abstract class וזה בתורו נורש ע“י פקדי התצוגה שלנו. צריך להזכיר שלא צריך לממש גם ממשק וגם abstract class, אלא אחד מהם. 

 /* Presentation Layer */
 public interface ITreeable
 { 
  TreeNode ReturnTreeNode();
 }
 
 abstract public class BaseContactPresentation : ITreeable
 {
  abstract public TreeNode ReturnTreeNode();
 }

ניישם את המחלקה הזו על ContactPresentation שתהיה אחראית על לממש את התצוגה של Contact: 

 /* Presentation Layer */
 public class ContactPresentation : BaseContactPresentation
 {
  private Contact _contact;
  public ContactPresentation(Contact curContact)
  {
   _contact = curContact;
  }
  public override TreeNode ReturnTreeNode()
  {
   return new TreeNode(_contact.DisplayName);
  }
 }

 

שימו לב, כל מה שעשינו זה לבנות קונסטרקטור שמקבל לתוכו Contact ומימשנו אפשרות להחזיר ענף בעץ אשר כתוב עליו את Contact.DisplayName. נעשה דבר דומה עם CG הרי כל CG הופכת בהכרח לענף בעץ לפי ContactGroup.DisplayName. אך, בנוסף לכך - נעבור על כל הפקדים ב-ContactItems ובהתאם לאיזה סוג האובייקט נשלח אותם לפקד המתאים שיחזיר לנו עבורם ענף בעץ.  

 /* Presentation Layer */
 public class ContactGroupPresentation :BaseContactPresentation
 {
  private ContactGroup _contactGroup;
  public ContactGroupPresentation(ContactGroup curContactGroup)
  {
   _contactGroup = curContactGroup;
  }
  public  override TreeNode ReturnTreeNode()
  {
   // Create New TreeNode from DisplayName
   TreeNode curTreeNode = new TreeNode(_contactGroup.DisplayName);
 
   // Add SubTreeNodes from ContactGroup.ContactItems
   for (int i=0; i<_contactGroup.ContactItems.Count; i++)
   {
    BaseContact curBaseContact = _contactGroup.ContactItems[i];
 
    // If current BaseContact gotten from ContactItems is Contact
    if (curBaseContact.GetType() == typeof(Contact))
     // Add TreeNode from ContactPresentation of Current BaseContact
     curTreeNode.Nodes.Add( new ContactPresentation((Contact)curBaseContact).ReturnTreeNode() );
 
    // If current BaseContact gotten from ContactItems is ContactGroup
    if (curBaseContact.GetType() == typeof(ContactGroup))
     // Add TreeNode from ContactGroupPresentation of Current BaseContact
     curTreeNode.Nodes.Add( new ContactGroupPresentation((ContactGroup)curBaseContact).ReturnTreeNode() );
   }
 
 
   // return current TreeNode
   return curTreeNode;
  }
 }
 

נציג דוגמה למימוש כל הקוד באפליקציה חלונאית (אשר קיים בה treeview בתצוגת עיצוב):  

 /* Presentation Layer */
  public Form1()
  {
   InitializeComponent();
   ContactGroup _inbox = new ContactGroup("inbox");
   _inbox.ContactItems.Add(new Contact("
a@a.com","Justin"));
   _inbox.ContactItems.Add(new Contact("
b@b.com","Yony"));
   _inbox.ContactItems.Add(new Contact("
c@c.com","TTTIS"));
   _inbox.ContactItems.Add(new Contact("
d@d.com","Grandma"));
   _inbox.ContactItems.Add(new ContactGroup("my Living Enemies"));
   ContactGroup InLineContactGroup = new ContactGroup("myFamily");
   InLineContactGroup.ContactItems.Add(new Contact("
mom@family.com","mom"));
   InLineContactGroup.ContactItems.Add(new Contact("
dad@family.com","dad"));
   _inbox.ContactItems.Add(InLineContactGroup);
             
   ContactGroupPresentation inbox = new ContactGroupPresentation(_inbox);
   tvwtreeView1.Nodes.Add(inbox.ReturnTreeNode());
  }

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

 

חלק ב': המציאות שלי

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

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

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

השלב הראשון של אפיון-עיצוב עם הלקוח הוא אחד מהעקרונות של Agile Software developement, לעבוד עם הלקוח ולתת לו הרגשה שהוא מבין באמת כל מה שאנו הולכים לעשות. כל תוצר שהלקוח רואה הוא צריך להיות יכול להבין בצורה מלאה בלי להשתמש במונחים שזרים לו (כגון: ”שחקנים”, “שאילתות”, “פקדים” וכיו”ב).

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

נשמע בלתי אפשרי? כנראה, אבל עם כמה שנות ניסיון בזה אני משתפר מפרוייקט לפרוייקט.

הפורמט הנוכחי של המסמך הוא:

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

מבחינת דרישות הלקוח התצוגה היא פר Feature (עוד על Features מתישהו במאמר על Feature-driven development). למשל: הוספת פרטי ספר, עריכת פרטי ספר, חיפוש ספר, תצוגת פרטי ספר, הוספת הוצאת ספרים, עריכת הוצאת ספרים, חיפוש הוצאת ספרים, תצוגת פרטי הוצאה וכך הלאה...

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

בנוסף הפירוט פר Feature מכיל רשימה טקסטואלית לחלוטין של Sub-Features של ה-Features הגדולים יותר. כאן זה המקום שנרשום את ה-Busniss Logic שלנו.

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

3. ERD של המסד נתונים. בנקודה שאתה כבר יודע איזה שדות יש לך במסך ואיזה כותרות ואיזה הכל - אין שום בעיה לכתוב ERD. עכשיו, בניגוד לכל שאר המסמך הזה שמלבד שהוא “מפת-דרכים“ לפרוייקט שלנו ונועד בעיקר ככלי אינטואטיבי להבנת האפליקציה - הERD אצלי הוא כלי עבודה חשוב ומהותי. כרגע אני כותב את ה-ERD בתוך תוכנת עיצוב מסדי נתונים בשם Erwin והוא מופיע במסמך עיצוב כקובץ בתוך מהמסמך. הסיבה היא שבחרתי Data-Driven development כצורת המחשבה שתקשר בין ה-Features האינטואטיביים לקוד של הפרוייקט.

 

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

א) Features במערכת:

1. יצירת איש-קשר חדש בטבלת אנשי-קשר.

2. יצירת קבוצת-קשר חדשה בטבלת קבוצות-קשר.

3. עריכת פרטי איש-קשר קיים בטבלת אנשי-קשר.

4. עריכת פרטי קבוצת-קשר קיימת בטבלת קבוצות-קשר.

5. בחירת אנשי-קשר לקבוצת-קשר קיימת בטבלה שמקשרת בין אנשי-קשר לקבוצות-קשר.

6. עריכת אנשי-קשר בקבוצת-קשר קיימת בטבלה שמקשרת בין אנשי-קשר לקבוצות-קשר.

6. תצוגת אנשי-קשר בתצוגת-עץ.

 

ב) ERD של המערכת - כתיבת Entity-Reletionship dIagram לשלושת הטבלאות (אנשי-קשר, קבוצות-קשר, וטבלה המקשרת בין אנשי-קשר וקבוצות-קשר). אחר כך הייתי מוסיף את הפונקציות שנובעות מה-Features. למשל אם עובדים עם Stored Procedures הייתי מוסיף לטבלת אנשי-קשר את contacts.AddNew ו-contact.EditExisting, לטבלת קבוצות-קשר הייתי מוסיף את contact_groups.AddNew ו-contact_groups.EditExisting, לטבלה שמקשרת בין שתי הטבלאות הללו הייתי יוצר את relation.AddRelation ואת relation.RemoveRelation. כמו כן הייתי מוסיף SP שמטרתה להחזיר טבלה שתתורגם בקלות ל-TreeView.

ג) כתיבת המסכים ב-Dot net forms ומקשר ביניהם ל-Stored Procedures. על חלק זה אפשר להרחיב ואני מבטיח לעשות זאת.

 

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

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

 

ברכות,

ג'סטין-יוסף אנג'ל

 

היסטורית שינויים:

24.7.2005 - בחלק א': יישום Composite Design Pattern, תודה לייוניי.

25.7.2005 - בחלק א': כתיבת Presentation layer. הוספת Strongly-typed collection.

10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
כל הכבוד !!!
<br>תמשיך להזריק לנו מהידע שלך...
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
המאמר נחמד, אבל:
<br>1. ההתנסחות חסרת סבלנות. התחושה היא שאתה לא מסביר בסבלנות, אלא מזדרז להציג את משנתך.
<br>2. העברית זקוקה לליטוש: &quot;שלושה שכבות&quot; ועוד מקומות שבהם זכר-נקבה לא זוכים להתאמה.
<br>3. הפונט גדול מדי. למה שלא תבחר בגודל שהוא פחות או יותר באותו גודל כמו שאר הטקסט בדף?
<br>4. הייתי מחלק את המאמר לשני מאמרים: על הפתרון שלך ועל צורת האפיון שלך.
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
לגבי החלק הראשון, כמו שנאמר בפורופ (תפוז), עדיף להשתמש
<br>ב-design pattern composite. הוא יותר מתאים.
<br>החלק השני של המאמר יותר טוב, ןהשאלה המעניינת איך משלבים, אם בכלל את ה-OOD של החלק הראשון (שוב, composite) עם החלק השני שיותר נוטה לכיוון ה-Data.
<br>
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
שלום לכולם,
<br>
<br>אלברטיטיס, תודה!
<br>
<br>עובר אורח,
<br>בנושא סעיפים 1 ו-4, מאמר התחיל כפתרון OOPי תמים לשאלה והתגלגל בסוף למה שהוא התגלגל. לא ידעתי בכלל עד אמצע המאמר לאיזה כיוון אני ממשיך איתו.
<br>2. אם יש טעויות ספציפיות אשמח לתקן.
<br>3. אם יש המלצה ספציפית לסוג פונט (גודל וסוג) אני אבדוק אותה. כרגע אני משתמש בזה כי זה פועל.
<br>
<br>אורח עובר,
<br>ביצעתי שינוי מקיף בחלק של ה-OOPי, אשמח לעוד תגובה עניינית. למען האמת, לא יצא לי לחשוב על השאלה שהעלת, ואני מבטיח לחפש תשובה.
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
שכבה זה לא נקבה? (שכבה יפה\מכוערת ;-) ) ונקבה סופרים שלוש, ארבע וכו'... לא?
<br>
<br>אני טיפש אבל נראה לי שעובר אורח קצת התבלבל\ת, לא?
12/2/2006 5:36:34 PM (Jerusalem Standard Time, UTC+02:00)
מאמר נחמד מאוד ומעניין אבל היה חלק קטן שלא הבנתי...

הקוד:
/* Busniss Logic Layer */
public class ContactGroup : BaseContact
{
private ContactItems contactItems;
public ContactItems ContactItems
{
get { return contactItems; }
set { contactItems = value; }
}

אתה עושה שבCG יש לך את הproperty של הcontactitems.
אתה עושה בset ש contactItems = value
אבל אני לא רואה מימוש לכך שאתה מכניס לסוג ContactItems משהו אז מה שנראה לי שיקרה זה שפשוט תחליף את ה ContactItems שלך לא?

נ.ב.
אני שונא לערב אנגלית ועברית, זה יוצא רע.
זיו
10/8/2007 6:24:53 AM (Jerusalem Standard Time, UTC+02:00)
היי ג'סטין.
יש לי כמה נקודות:
1. עצה: אולי את קטעי הקוד תעלה לאתר שלך בתצורת תמונה וכך נוכל לראות את צורת הקוד שאנו מכירים מה-Visual Studio ולא רשימת אותיות בשחור לבן. (אני מתכנת ב-VB אז העיניים שלי מתעייפות מהר מ-C#)
2. מאמר פצצה אבל (ויש אבל) תעלה בבקשה תמונה או קובץ של שרטוט ה-ERD ותפרט יותר בנושא העיצוב.
3. מזמן לא כתבת משהו. יש כבר כ"כ הרבה דברים חדשים : דוט נט 3.0, Visual Studio 2008 ועוד, אז תמצא זמן... :-)
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):