82. Interface explicit implementation

פעמים רבות אנחנו מממשים ממשקים.

ניתן לממש ממשק בשתי דרכים: explicitly וimplicitly.

הדרך שאנחנו משתמשים בה בד”כ היא מימוש implicitly:

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

הממשק

1
2
3
4
public interface IFruit
{
string Name { get; }
}

עם המימוש

1
2
3
4
5
6
7
8
9
10
public class Banana : IFruit
{
public string Name
{
get
{
return "Banana";
}
}
}

Explicit implementation:

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

1
2
3
4
public interface IIsraeliProduct
{
string Name { get; }
}

עכשיו יש לנו טיפוס שמממש את שני הממשקים:

1
2
3
4
5
6
7
8
9
10
public class Banana : IFruit, IIsraeliProduct
{
public string Name
{
get
{
return "Banana";
}
}
}

עדיין עובד, אבל אולי אנחנו רוצים משהו אחר, למשל לעשות שאם אנחנו משתמשים בIIsraeliProduct, יחזור לנו שם המוצר בעברית.

נוכל לעשות זאת באמצעות Explicit implementation: הרעיון הוא שאנחנו משייכים את המימוש לממשק, למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Banana : IFruit, IIsraeliProduct
{
string IFruit.Name
{
get
{
return "Banana";
}
}
string IIsraeliProduct.Name
{
get
{
return "בננה";
}
}
}

כעת נוכל להשתמש במימוש שמעניין אותנו ע"י הכנסה לinstance מType מתאים:

1
2
3
4
5
Banana banana = new Banana();
IFruit fruit = banana;
IIsraeliProduct product = banana;
Console.WriteLine(fruit.Name); // Banana
Console.WriteLine(product.Name); // בננה

נשים לב לכמה דברים מעניינים:

  1. אנחנו לא מציינים modifier (private/public וכו’) על המימוש. הסיבה היא שהמימוש הוא "מימוש פרטי", כלומר לא ניתן לגשת למימוש זה בלי להסב את הטיפוס לממשק שהמתודה שייכת לו
  2. לא נוכל לקרוא למימוש בלי הסבה לממשק הנתון

שימושים:

  1. יצירת שני מימושים שונים לשתי פונקציות עם אותה חתימה, בהתאם לinterface. בד"כ עדיף שכל המימושים הפרטיים יחזירו אותו ערך, אבל לפעמים זה יכול לעזור.
  2. הסתרת מתודה ממחלקה. לפעמים מכריחים אותנו לממש ממשק, אבל אנחנו לא מעוניינים שהמשתמש יוכל להשתמש בפונקציה של הממשק. כך אנחנו מסתירים לו אותה, ורק אם הוא יסב את המשתנה לטיפוס של הממשק, הוא יוכל לגשת אליה. נפוץ מאוד כאשר מממשים גם גרסה גנרית וגם גרסה לא גנרית של ממשק. בד"כ נסתיר את הגרסה הלא גנרית.
  3. מימוש שני ממשקים עם מתודה בעלת אותו שם וחתימה, עד כדי ערך החזר. לדוגמה, נניח שיש לנו את שני הממשקים הבאים:
1
2
3
4
5
6
7
8
9
public interface INamesContainer
{
IEnumerable<string> Value { get; }
}
public interface IPksContainer
{
IEnumerable<ulong> Value { get; }
}

אז לא נוכל לממש את שניהם, אלא נשתמש במימוש פרטי (מאחר ואי אפשר לעשות שתי מתודות עם אותו שם וחתימה עד כדי ערך החזר):

1
2
3
4
5
6
7
8
9
10
11
12
public class MyContainer : INamesContainer, IPksContainer
{
IEnumerable<ulong> IPksContainer.Value
{
get { return null; }
}
IEnumerable<string> INamesContainer.Value
{
get { return null; }
}
}

ראו גם טיפ 285 על איך Java מסתדרת בלי Explicit Implementation.

יום ממומשק טוב

שתף