81. Type encapsulation

אחד הדברים המציקים שאני רואה לא מעט בזמן האחרון זה קוד מהצורה הבאה:

1
2
3
4
public List<MyClass> MyFunction()
{
// ...
}

או יותר גרוע:

1
2
3
4
public void MyFunction(List<MyClass> givenList)
{
// ...
}

הסבר:

הפונקציה הראשונה מחזירה List<MyClass>, הפונקציה השנייה מקבלת List<MyClass>.

מה הבעיה בזה?

במידה ואנחנו מחזירים List<MyClass>, בעצם אין פה שום אנקפסולציה של הType שאנחנו מחזירים.

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

הפתרון:

נחזיר IEnumerable<MyClass> במקום, ובמידה ואנחנו צריכים יותר מרק IEnumerable, נחזיר ICollection<MyClass> או אפילו IList<MyClass>.

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

בקשר לפונקציה השנייה, שם יש בעיה יותר גדולה:

הפונקציה מקבלת List<MyClass>.

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

לרוב אנחנו בכלל לא נצטרך את המבנה של List<MyClass> ונוכל להסתפק בIEnumerable<MyClass> (למטרות ריצה בלבד), או לכל היותר ICollection<MyClass> (למטרות יותר מורכבות, כגון שינוי האוסף, ספירת גודלו, בדיקה אם הוא מכיל איבר וכו’)

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


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

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

שבוע ממומשק טוב

שתף