#C: טיפוסים גנריים (Generic Types) וממשקים (Interfaces)

שייך לקטגוריות .NET, C#

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

public class ItemCollection<T> : List<T> {
	public ItemCollection<T> Load() {
		// load process here...
		return this;
	}
	public ItemCollection<T> Attach(T item) {
		base.Add(item);
		return this;
	}
	public ItemCollection<T> Clone() {
		ItemCollection<T> cloned=new ItemCollection<T>();
		// clone logic here...
		return cloned;
	}
}

והתוכנית הבאה:

public class Program {
	public static void Main(string[] args) {
		ItemCollection<int> intCollection=new ItemCollection<int>();
		intCollection.Attach(1).Load();
	}
}

ItemCollection היא מחלקה שיורשת מ- List<T> ומוסיפה לה כמה מתודות. שימו לב שגם היא מקבלת T כ-type parameter ומעבירה אותו ל-List עצמו, כלומר תינתן לנו האופציה ליצור מופע עם כל טיפוס כפרמטר, ובתוך המחלקה עצמה נמשיך להתייחס אליו כאילו הוא T.

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

בתוכנית, יצרנו מופע של ItemCollection מסוג int, הפעלנו עליו את המתודה Attach שבמחלקה עצמה מקבלת T אך למופע שלנו ה-T ייחשב כ-int, עם הפרמטר 1. המתודה Attach החזירה את האובייקט עצמו ועכשיו אנחנו יכולים להפעיל עליו עוד מתודות, כמו Load.

ועכשיו, נניח שיש לנו עוד מחלקת עזר שמקבלת ItemCollection כזה ומוסיפה לו ערך.

public class Helper {
	public void AddToItemCollection(ItemCollection<int> collection,int item) {
		collection.Attach(item);
	}
}

ועכשיו נוכל להעביר לה כפרמטר את האוסף ומספר ולהוסיף לו אותו

Helper.AddToItemCollection(intCollection,2);

אחלה! רק חבל שזה מתאים רק ל ItemCollection<int> . אם יהיה לנו ItemCollection<string>, נאלץ לכתוב מתודה נוספת. אז אנחנו יכולים להפוך את המתודה הזו לגנרית אף היא:

	public static void AddToItemCollection<T>(ItemCollection<T> collection,T item) {
		collection.Attach(item);
	}

ולהוסיף לכל סוג של ItemCollection

		ItemCollection<string> stringCollection=new ItemCollection<string>();
		Helper.AddToItemCollection<string>(stringCollection,"hello");

כיף. אבל, בכל פעם שנרצה להעביר את המחלקה כפרמטר נאלץ להשתמש במתודות גנריות. תסכול מס' 1 :-S (האייקון הממורמר של מסנג'ר פה).

אהה! נממש interface עם המתודות האלה, מה כבר יכולה להיות הבעיה…

public interface IItemCollection {

}

ופה ניתקע. כאן המוח שלנו יתחיל להיכנס ללולאה אינסופית. אני רוצה להגדיר את המתודה Load, מה לעזאזל ערך ההחזרה שלה? נו בוודאי, ItemCollection<… אבל איזה T? אולי נעשה את הממשק גם הוא גנרי? יאללה, רק שלא נרוויח מזה מאום, כי גם כשנרצה להעביר את הממשק כפרמטר הטיפוס שלו יצטרך להכיל את פרמטר הטיפוס או לחילופין להיות גנרי. תסכול מס' 2 :-S.

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

public interface IItemCollection {
	IItemCollection Load();
	IItemCollection Attach(object item);
	IItemCollection Clone();
}

אם המחלקה שלנו תממש את הממשק הזה המתודות שלה יוכלו להחזיר את הממשק עצמו, מתודות שמתייחסות לטיפוס T יועברו כ-object ויעברו פעולת casting קטנה.

public class ItemCollection<T> : List<T>,IItemCollection {
	public IItemCollection Load() {
		// load process here...
		return this;
	}
	public IItemCollection Attach(object item) {
		base.Add((T)item);
		return this;
	}
	public IItemCollection Clone() {
		ItemCollection<T> cloned=new ItemCollection<T>();
		// clone logic here...
		return cloned;
	}
}

עכשיו זה באמת טוב. מימשנו את הממשק, T הופך ל-object והרבה קאסטים גועליים רצים לנו בתוך הלוגיקה. תסכול מס' 3 :-S.

Explicit implementation to the rescue

למה שלא נמשיך להתעקש? בסוף תמיד נגיע למחוז חפצנו. בדוט נט אפשר להגדיר explicit implementation על interface, מה שנותן לנו את האופציה אכן להגדיר את המימוש למתודה אבל רק כאשר המחלקה שלנו מזוהה כאותו ממשק. נקפוץ לקוד:

public class ItemCollection<T> : List<T>,IItemCollection {
	public ItemCollection<T> Load() {
		// load process here...
		return this;
	}
	public ItemCollection<T> Attach(T item) {
		base.Add(item);
		return this;
	}
	public ItemCollection<T> Clone() {
		ItemCollection<T> cloned=new ItemCollection<T>();
		// clone logic here...
		return cloned;
	}

	IItemCollection IItemCollection.Load() {
		return Load();
	}
	IItemCollection IItemCollection.Attach(object item) {
		return Attach((T)item);
	}
	IItemCollection IItemCollection.Clone() {
		return Clone();
	}
}

כעת, המחלקה שלנו כוללת שני מימושים למתודות. ממשקים מחייבים הגדרה אחת של מתודה עם חתימה ספציפית. המימוש של Load שתופס עבור ה-interface הינו המימוש המחזיר IItemCollection. שימו לב לנוכחות ה IItemCollection+נקודה לפני המילה Load – זה אומר שזה המימוש של Load שאותו ממשק דורש. למימוש השני אין קשר לממשק, והוא לא מתנגש עם הממשק. מעתה נוכל להתייחס ל- IItemCollection<> גם כממשק ולהעביר אותו למתודות ללא בעיה.

קוד מתוך התוכנית

		IItemCollection doubleCollection=new ItemCollection<double>();
		Helper.AddToItemCollection(doubleCollection,3.4);
		Logger.Dump(doubleCollection);

המתודה מתוך ה-Helper

	public static void AddToItemCollection(IItemCollection collection,object item) {
		collection.Attach(item);
	}

דוגמה נוספת

public interface IFirst {
	IFirst A();
}
public interface ISecond {
	ISecond A();
}

public class Class : IFirst,ISecond {
	IFirst IFirst.A() {
		Logger.Write("IFirst.A");
		return this;
	}
	ISecond ISecond.A() {
		Logger.Write("ISecond.A");
		return this;
	}
}

public class Helper {
	public static void PrintFirstA(IFirst first) {
		first.A();
	}
	public static void PrintSecondA(ISecond second) {
		second.A();
	}
}

קוד מתוך התוכנית:

		Class c=new Class();
		Helper.PrintFirstA(c);
		Helper.PrintSecondA(c);
		((IFirst)c).A();

ידפיס על המסך

IFirst.A
ISecond.A
IFirst.A

שימו לב שאין מימוש public ל-A אלא מימוש explicit של interface. כל interface מכיל את המתודה A ו-Class נותן מימוש אחר לכל ממשק, כך שכשנתייחס למחלקה כ-IFirst היא תפעיל את IFirst.A וכנ"ל לגבי ISecond.



קטגוריות

חיפוש

עיקבו אחרי (אקספרימנטלי!)

תגובה אחת

כתיבת תגובה

16.10.07 בשעה 12:40
Admini
(אתר)

מעניין.

1

כתיבת תגובה

תגיות מותרות לשימוש בתוכן
XHTML: אפשר להשתמש בתגים הללו: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre>