285. Explicit interface implementation and your cup of coffee

[מבוסס על השאלה הזאת מStackOverflow]

משהו שבוודאי שמנו לב אליו בפעמים הקודמות, הוא שבמידה ויש לנו שתי פונקציות עם אותו שם, ואותם פרמטרים, הדבר עשוי להיות בעייתי במספר מצבים.

(אנחנו נתקלנו בבעיה עם Indexerים ספציפית, אבל קל להיתקל בזה גם בהקשרים אחרים)

בC# מה שעשו, זה אפשרו לעשות מימוש של Explicit Implementation לממשקים, המאפשר לנו לציין לאיזה ממשק שייכת המתודה שאנחנו רוצים לממש. (ראו גם טיפ מספר 82)

אולי תתפלאו לשמוע, אבל יש שפות בהן הדבר הזה לא אפשרי, למשל שפת Java.

בJava אי אפשר לתת לשתי מתודות אותו שם ואתם פרמטרים, ולמעשה גם אין שם Explicit Implementation.

אז איך הם מסתדרים שם?

דבר ראשון, לJava יש תמיכה בCovariance במימוש ממשקים, למה הכוונה?

נניח שיש לנו איזשהו טיפוסים שמממש כמה ממשקים:

1
2
3
public class Dog : IBarkable, IMoveable
{
}

עכשיו נניח שיש לנו כמה Providerים:

1
2
3
4
5
6
7
8
9
public interface IBarkableProvider
{
IBarkable Provide();
}
public interface IMoveableProvider
{
IMoveable Provide();
}

עכשיו נניח שאנחנו רוצים לממש את שני הממשקים האלה של הProviderים. בC# אין לנו ברירה, ואנחנו חייבים לממש אותם Explicitly:

1
2
3
4
5
6
7
8
9
10
11
12
public class Provider : IBarkableProvider, IMoveableProvider
{
IBarkableIBarkableProvider.Provide()
{
// ...
}
IMoveableIMoveableProvider.Provide()
{
// ...
}
}

בJava יש אלטרנטיבה! אפשר לממש פונקציה אחת שמחזירה את שני הטיפוסים!

1
2
3
4
5
6
7
public class Provider :IBarkableProvider, IMoveableProvider
{
public Dog Provide()
{
// ...
}
}

שימו לב שיש פה Covariance בערך ההחזר, כלומר עצם העובדה שDog מממש את שני הממשקים שלנו, גורם לכך שגםProvider מממש את שני הממשקים.

ראו גם טיפים מספר 36-40 על Covariance וContravariance.


מה עוד?

הרי לא תמיד הסיטואציה כל כך פשוטה, למשל אם ערכי ההחזר הם string וint, אי אפשר לעשות טריק כזה.

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

1
2
3
4
5
6
7
8
9
10
public class Provider
{
public IBarkableProvider AsBarkableProvider()
{
}
public IMoveableProvider AsMoveableProvider()
{
}
}

ויוצרים מחלקות פנימיות שמממשות אותן:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Provider
{
private class BarkableProvider : IBarkableProvider
{
private readonly Provider mProvider;
public BarkableProvider(Provider provider)
{
mProvider = provider;
}
public IBarkable Provide()
{
// ...
}
}
private class MoveableProvider : IMoveableProvider
{
private readonly Provider mProvider;
public MoveableProvider(Provider provider)
{
mProvider = provider;
}
public IMoveable Provide()
{
// ...
}
}
public IBarkableProvider AsBarkableProvider()
{
return new BarkableProvider(this);
}
public IMoveableProvider AsMoveableProvider()
{
return new MoveableProvider(this);
}
}

מה הטריק? הטריק הוא שהמחלקות הפנימיות יכולות לגשת לכל הMemberים של המחלקה Provider, ולכן לממש את הממשק כאוות נפשן! 😃

(ראו גם טיפ מספר 217)

אמנם בC# אנחנו יכולים לפתור את הבעיות האלה בצורה אחרת בעזרת Explicit implementation, אך אפשר להעתיק את הרעיון למספר למקומות שבהם הוא מתאים.

יום עם פולי קפה טובים טוב

שתף