84. Extension methods and interfaces

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

יש לזה חסרון, מכיוון שזה מכריח את מי שמממש את הממשק שלנו לממש הרבה פונקציות.

למשל, ראו את הדוגמה הבאה:

קיים ממשק המייצג גוף המסוגל להסתובב בכל כיוון:

1
2
3
4
5
6
public interface ITurnable
{
void TurnLeft();
void TurnOpposite();
void TurnRight();
}

הפונקציה TurnLeft – גורמת לגוף להסתובב 90 מעלות שמאלה

הפונקציה TurnOpposite – גורמת לגוף להסתובב ב180 מעלות

הפונקציה TurnRight – גורמת לגוף להסתובב ב90 מעלות ימינה

בסה"כ ממשק די פשוט, אלא שכדי לממש אותו, המממש צריך לממש 3 מתודות, מה שמדכא אותו לממש את הממשק הזה.

עוד דוגמה היא כזאת: נניח שיש לנו ממשק עם מתודה:

1
2
3
4
5
6
public interface IMailSender
{
void SendMessage(string address, string subject);
void SendMessage(string address, string subject, string content);
void SendMessage(string address, string subject, string content, object[] attachments);
}

(ודמיינו עוד כמה overloadים)

המממש של ממשק זה יהיה מתוסכל, לא רק שהוא צריך לממש מתודה עם 4 ארגומנטים, אלא הוא גם צריך לממש 3 מתודות במחלקה. ועוד הן בסה"כ קוראות לחתימה הארוכה.

בעיות אלה אפשר לפתור בצורה אלגנטית באמצעות extension methods:

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

1
2
3
4
public interface ILeftTurnable
{
void TurnLeft();
}

ונוסיף שני extension methods כנ"ל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class TurnableExtensions
{
private static void TurnLeft(this ILeftTurnable turnable, int times)
{
for (int i = 0; i < times; i++)
{
turnable.TurnLeft();
}
}
public static void TurnOpposite(this ILeftTurnable turnable)
{
turnable.TurnLeft(2);
}
public static void TurnRight(this ILeftTurnable turnable)
{
turnable.TurnLeft(3);
}
}

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

בדוגמה השנייה, הדבר עוד יותר מתאים. במקום שנדרוש מהמממש לממש לנו הרבה פונקציות שבסה"כ קוראות לפונקציה אחת, נדרוש ממנו לממש רק את הפונקציה הארוכה:

1
2
3
4
public interface IMailSender
{
void SendMessage(string address, string subject, string content, object[] attachments);
}

ואת הגישה לשאר הפונקציות נעשה עם extension methods:

1
2
3
4
5
6
7
8
9
10
11
12
public static class MailSenderExtensions
{
public static void SendMessage(this IMailSender sender, string address, string subject)
{
sender.SendMessage(address, subject, null);
}
public static void SendMessage(this IMailSender sender, string address, string subject, string content)
{
sender.SendMessage(address, subject, content, null);
}
}

כך הרווחנו שני דברים:

למממש של המחלקה יש פחות עבודה לעשות, ואילו הממשק עדיין נוח למשתמשים בו.

כדאי לשים את הExtension Methods הנ"ל בNamespace של הממשק שלכם כדי שמי שמשתמש בממשק, ימצא את זה אוטומטית עם שימוש בממשק.


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

כדי לתמוך בזה, ניתן לעשות משהו כזה:

ניצור ממשק נוסף:

1
2
3
4
public interface IRightTurnable
{
void TurnRight();
}

כעת נתחשב בממשק זה בExtension Method שכתבנו:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void TurnRight(this ILeftTurnable turnable)
{
IRightTurnable rightTurnable =
turnable as IRightTurnable;
if (rightTurnable != null)
{
rightTurnable.TurnRight();
}
else
{
turnable.TurnLeft(3);
}
}

כך נתנו מצד אחד אפשרות לא לממש את הממשק, ולקבל מימוש דיפולטי, ומצד שני לממש את הממשק ולקבל את המימוש הרגיל.

המשך ערב ממומשק טוב

שתף