380. Strategy design pattern

בהמשך לטיפים הקודמים,

נדבר הפעם על הDesign pattern ששמו Strategy.

הרעיון של הDesign pattern הוא מימוש של חלק ממחלקה באמצעות מחלקה אחרת.

הדבר מאפשר החלפת חלק מההתנהגות של המחלקה באמצעות הזרקה של מחלקה אחרת.


לדוגמה, נניח שיש לנו את הממשק הזה:

1
2
3
4
5
public interface ICommutativeCalculator
{
int Add(int a, int b);
int Multiply(int a, int b);
}

נוכל ליצור מימוש מוכוון Strategy שלו כך:

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
public class StrategyBasedCommutativeCalculator : ICommutativeCalculator
{
private readonly INumberAdder mAdder;
public StrategyBasedCommutativeCalculator(INumberAdder adder)
{
mAdder = adder;
}
public int Add(int a, int b)
{
return mAdder.Add(a, b);
}
public int Multiply(int a, int b)
{
int result = 0;
for (int i = 0; i < b; i++)
{
result = Add(result, a);
}
return result;
}
}

כאשר הממשק INumberAdder נראה כך:

1
2
3
4
public interface INumberAdder
{
int Add(int a, int b);
}

כעת אנחנו יכולים לאפשר שינוי ההתנהגות של הCalculator ע"י מימושים שונים של NumberAdder, למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SlowNumberAdder : INumberAdder
{
public int Add(int a, int b)
{
int result = a;
for (int i = 0; i < b; i++)
{
result++;
}
return result;
}
}

או מימוש טיפה יותר טוב 😃

1
2
3
4
5
6
7
public class BetterNumberAdder : INumberAdder
{
public int Add(int a, int b)
{
return a + b;
}
}

וכך נוכל להחליף את ההתנהגות של הפונקציה שלנו ע"י שינוי הAdder מבחוץ:

1
2
3
4
5
ICommutativeCalculator slowerCalculator =
new StrategyBasedCommutativeCalculator(new SlowNumberAdder());
ICommutativeCalculator slowCalculator =
new StrategyBasedCommutativeCalculator(new BetterNumberAdder());

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


הדבר שימושי במידה ויש לנו מחלקה שמשתמשת באלגוריתם ואנחנו רוצים לאפשר לשפר או לשנות את האלגוריתם מבחוץ. כמובן, לא בהכרח חייב להיות לנו אלגוריתם מתמטי, אלא יכול להיות מדובר גם ב"שירות", למשל שירות שמספק לנו אפשרות לשלוף מDatabase.

בנוסף, הדבר יכול להיות שימושי אם נוכל לקנפג את הStrategy בו אנחנו משתמשים מבחוץ – כך נוכל להחליף התנהגות של מחלקה מבלי לשנות קוד.

הדבר גם מאפשר Mocking בחלק מהמקרים.

אני מניח שכולנו השתמשנו בדברים כאלה, רק לא ידענו שקוראים לזה Strategy.

סופ"ש מלא אסטרטגיה טוב.

שתף

379. Default arguments values vs object initializer

הכרנו בעבר את הObject Initializer המאפשר לאתחל ערכים של המחלקה בצורה נוחה. (ראו גם טיפ מספר 87)

בהמשך הכרנו גם את הFeature של Default Value Arguments (הFeature של C# 4.0 – ראו גם טיפ מספר 243)

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

  • אופציה ראשונה – להעביר את כל הערכים בConstructor, ולסמן את הערכים שיש להם ערכים דיפולטיים עם Default argments
  • אופציה שניה – לאתחל את כל הProperties של המחלקה בעזרת Object Initializer (ולאתחל את יתר השדות בערכים הדיפולטיים שנקבע)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Person
{
public Person()
{
IsMarried = false;
NumberOfChildren = 0;
}
public Person(string name, bool isMarried= false, int numberOfChildren = 0)
{
Name = name;
IsMarried = isMarried;
NumberOfChildren = numberOfChildren;
}
public string Name { get; set; }
public bool IsMarried { get; set; }
public int NumberOfChildren { get; set; }
}

ניתן לאתחל בשתי דרכים:

הדרך הראשונה:

1
Person person = new Person(name: "Yosi", isMarried: true);

הדרך השנייה:

1
2
3
4
5
Person person = new Person()
{
Name = "Yosi",
IsMarried = true,
};

נשווה בין שתי השיטות:

היתרון בשיטה הראשונה היא שהיא מאפשרת לנו שהFields של המחלקה יהיו readonly (ראו גם טיפ מספר 14), בנוסף היא יכולה לחייב אותנו לאתחל פרמטר מסוים.

החסרונות שלה הם הבאים:

  • נוכל לשים בתור Default argument value רק ערכים שהם Compile-time
  • בכל פעם שנשנה את הערכים הדיפולטיים, נצטרך לקמפל מחדש את כל הDLLים שמשתמשים במחלקה שלנו – זאת מאחר וDefault argument values נכתבים Hard-coded בתוך הקוד בזמן קימפול (ראו גם טיפ מספר 244)

היתרון של השיטה השנייה היא שנוכל לשים בתור Default argument value גם ערכים שהם לא Compile-time, ובמידה ונשנה ערך דיפולטי, זה לא יגרור קימפול מחדש של DLL שמשתמש במחלקה שלנו. החסרון הוא, כמובן, שהProperty הוא לא readonly (במידה ונרצה שהוא readonly), ושאנחנו לא יכולים לחייב את המשתמש לאתחל שדות מסוימים, אלא אם נקבל אותם בConstructor.

המשך יום דיפולטי שמאתחל אובייקטים טוב.

שתף

378. Method class design pattern

קיים Design pattern נחמד וחזק שממיר פונקציות למחלקות. הרעיון הוא כזה:

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

1
2
3
4
public static IEnumerable<Person> GetPeopleByNameAndAge(string name, int age)
{
// ...
}

ניצור בנוסף ממחלקה שנראית ככה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class GetPeopleByNameAndAgeOperation
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
public IEnumerable<Person> Execute()
{
// Call the original method
return GetPeopleByNameAndAge(this.Name, this.Age);
}
}

בעצם יצרנו מחלקה שמכילה את כל הארגומנטים שהפונקציה מצפה לקבל ויש לה פונקציה Execute שמבצעת את הפעולה.

נוכל למשל לקרוא לפונקציה כך:

1
2
3
4
5
6
7
8
GetPeopleByNameAndAgeOperation operation =
new GetPeopleByNameAndAgeOperation()
{
Name = "Asaf",
Age = 28
};
IEnumerable<Person> results = operation.Execute();

למה זה טוב? כרגע זה נראה שזה טיפה יותר מסובך מקריאה פשוטה לפונקציה.

אז ככה:

  • קודם כל אנחנו יכולים לראות את השמות של הפרמטרים בצורה יותר ברורה
  • דבר שני, הסדר של הפרמטרים לא משנה יותר – כי הפרמטרים הם Named
  • בנוסף, אנחנו יכולים לתת ערכים דיפולטיים לפרמטרים (שימו לב שאת היתרונות האלה אנחנו מקבלים גם מNamed arguments וDefault argument values של C# 4.0)
  • היתרון המשמעותי של הDesign pattern הזה הוא שאנחנו לא חייבים להריץ את הפונקציה ישר, אלא אנחנו יכולים להריץ אותה גם בהמשך. הדבר הזה מאפשר את היתרונות הבאים:
    • אנחנו יכולים לשנות פרמטרים לפני ההרצה – אם למשל חלק מהפרמטרים לא ידועים לנו בשלב כלשהו של התכנית שלנו, אבל חלק כן, אנחנו יכולים למלא בכל חלק בתכנית את הפרמטרים שאנחנו מכירים ולהריץ את המתודה כשמילאנו את כל הפרמטרים
    • בנוסף, האופן בו אנחנו מריצים את הפונקציה הוא נתון לבחירתנו! אנחנו יכולים להחליט אם אנחנו מעוניינים בהרצה סינכרונית, או א-סינכרונית, אולי בכלל נפנה לשירות – על כל זאת אנחנו יכולה להשפיע ע"י מימוש פונקציה הרצה משלנו בתוך המחלקה!

אישית יצא לי להשתמש בPattern הזה בהקשרי GUI (יצירת Commandים) ובהרצה של Stored Procedures ע"י מחלקות Strongly-typed.

מומלץ בחום 😃

המשך יום עם מתודות שהן מחלקות טוב.

שתף

377. OptionalAttribute DefaultParameterValueAttribute

מסתבר שקיימת עוד דרך בFramework להגדיר על פרמטרים ערכים דיפולטיים. הדרך היא באמצעות Attributeים על פרמטרים (ראו גם טיפ מספר 246):

1
2
3
4
public static int MyMethod(int number, [Optional][DefaultParameterValue("Berry Sakaroff")]string name)
{
//
}

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

אז מה זה עושה בכלל? אם נריץ את הפונקציה בReflection (כמו בטיפ הקודם) עם Missing.Value, נקבל את הערך הדיפולטי בפרמטרים שלא ציינו.

הFeature הזה הוא Cross-Language (הוא מתאים לכל השפות שכתובות מעל הFramework) וכנראה לא יותר מדי שימושי מאחר ובC# 4.0 יש כבר דרך טובה יותר להגדיר ערכים דיפולטיים, אבל נחמד להכיר שיש דבר כזה.

דרך אגב, אם נסתכל בReflection על מתודה שקימפלנו בC# 4.0 עם פרמטרים דיפולטיים, נראה שיש מעל הפרמטרים הדיפולטיים את הAttribute ששמו Optional, אבל אין את DefaultParameterValue. מעניין העניין.

סופ"ש דיפולטי אחר וטוב.

שתף

376. Missing Value

בהמשך לפעם שעברה,

נניח שיש לנו מתודה עם ערכים דיפולטיים:

1
2
3
4
public static int MyMethod(int number, string name = "Berry Sakaroff", DateTime date = default(DateTime))
{
//
}

ואנחנו רוצים להריץ אותה בReflection עם חלק מהערכים.

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

דרך נחמדה אחרת היא באמצעות משהו שנקרא Missing.Value: פשוט נעביר אותו בתור כל הפרמטרים שאנחנו לא מעוניינים לציין:

1
2
3
4
5
6
7
MethodInfo method =
typeof (MethodType).GetMethod("MyMethod", BindingFlags.Public | BindingFlags.Static);
object value =
method.Invoke(null, new object[] {3, Missing.Value, DateTime.Now});
// MyMethod(3, "Berry Sakaroff", DateTime.Now)

ככה לא צריך לחפש את הערכים הדיפולטיים בעצמנו.

המשך יום דיפולטי טוב.

שתף

375. Accessing default arguments values

ראינו בטיפ מספר 243 את הFeature שנוסף בC# 4.0 המאפשר לנו לציין ערכים דיפולטיים למתודה שלנו, מבלי לייצר הרבה Overloadים.

ראינו שמאחורי הקלעים מה שנעשה הוא פשוט הכנסה Hard-Coded של ערכים אלו בכניסה לפונקציה.


קיים באינטרנט Framework בשם ASP.net MVC 4. Framework זה מאפשר לנו לבנות אתרי אינטרנט בצורה מאוד נוחה. (אם מישהו מכיר Ruby on rails – Asp.net MVC הוא החיקוי של מיקרוסופט לROR)

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

http://localhost:2134/pet/getage/Dog

תתבצע גישה למחלקה PetController למתודה GetAge עם הפרמטר “Dog”.

בנוסף אם ניגש ל

http://localhost:2134/pet/getlastpets/3?type=Dog

יבצע לנו קריאה למחלקה PetController למתודה GetLastPets עם הארגומנטים “Dog” ו3.


מה שמעניין בכל הסיפור הזה זה שאם יש לנו ארגומנטים עם ערכים דיפולטיים – אז התשתית תדע לקרוא למתודה שלנו עם ערכים דיפולטיים. כלומר אם ניגש לUrl הזה

http://localhost:2134/pet/getlastpets/3

ויש למתודה GetLastPets ערך דיפולטי עבור הפרמטר type, תתבצע גישה עם הערך הדיפולטי של type.

רציתי לדעת איך הדבר הזה מממומש.

מסתבר שבReflection יש לParameterInfo את הProperties הבאים:

1
2
3
4
5
6
7
ParameterInfo[] parameters = methodInfo.GetParameters();
ParameterInfo parameter = parameters[1];
bool isOptional = parameter.IsOptional;
object defaultValue = parameter.DefaultValue;

מה שקורה זה שIsOptional מחזיר לנו האם לפרמטר יש ערך דיפולטי, וDefaultValue מביא לנו את הערך הדיפולטי.


אם תסתכלו אחורה, תראו שגם בFrameworkים הקודמים היו את הProperties האלה. איך זה יכול להיות?

ובכן, Reflection הוא לא ספציפי לC# - מסתבר שיש שפה שהיא אחות של C# בשם VB.net, שם היו פרמטרים דיפולטיים גם לפני Framework 4.0, וזו הייתה הדרך לגשת אליהם.


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

שבוע עם פרמטרים דיפולטיים טוב.

שתף

374. Java 8 Lambda expressions

בהמשך לטיפים על הגרסה הבאה של Java, להלן טיפ על הFeature האחרון שמתווסף לשפה בJava 8.

הFeature נקרא Lambda Expressions, והוא מוכר לנו מC# (ראו גם טיפ מספר 45).

ראינו בטיפ מספר 370 הסבר על Single abstract methods (SAM).

אחת השיטות (וכנראה השיטה העיקרית) לאתחל Single abstract method היא באמצעות Lambda Expressions:

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

1
2
3
public interface Comparator<T> {
public int compare(T o1, T o2);
}

נניח שאנחנו רוצים ליצור מימוש שלו.

עד Java 8 היינו צריכים לכתוב משהו כזה:

1
2
3
4
5
6
7
Comparator<String> comparator =
new Comparator<String>() {
@Override
public int compare(String x, String y) {
return (x.length() - y.length());
}
};

נוכל ליצור מימוש שלו בצורה הבאה:

1
Comparator<String> comparator = (x, y) -> (x.length() - y.length());

הסינטקס דומה לזה של C#, רק שכאמור אין delegate אלא SAMים.

הדבר הזה גם מאפשר להעביר את הLambdaות האלה להרבה מהפונקציות של הFramework של Java שמצפות לקבל ממשקים כאלה, למשל, נוכל לעשות משהו כזה:

1
2
3
4
public static void sortTeamsByLength(String[] teams) {
Comparator<String> comparator = (x, y) -> (x.length() - y.length());
Arrays.sort(teams, comparator);
}

או אפילו ישר ככה:

1
2
3
public static void sortTeamsByLength(String[] teams) {
Arrays.sort(teams, (x, y) -> (x.length() - y.length()));
}

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

סופ"ש אנונימי טוב.

שתף

373. Virtual Extension methods

בהמשך לטיפ על Single Abstract Method הבא אליו בJava 8, הנה עוד Feature שמגיע אלינו מJava 8:

הFeature נקרא Virtual Extension Methods.

על Extension Methods בC# דיברנו כבר לא מעט (טיפים 66-75 וגם אחר כך).

בגדול Extension Methods נותנים לנו שני דברים מגניבים:

  • אופציה “להוסיף” מתודות לTypeים שכבר קיימים (טיפ מספר 75 למשל)
  • אופציה לקבל בחינם מתודות לממשקים ע”י שימוש בפונקציות שהם חושפים (טיפ מספר 84)

היכולת הראשונה יכולה לאפשר לנו יכולות יפות (כמו הרבה מהדוגמאות שראינו), אבל יכולה גם לזבל לנו את הIntellisense בExtension Methods לא רלוונטיים במידה והוספנו Reference ועשינו using לא נכון.

בנוסף, ייתכן כי נשתמש בExtension Method שאין לו “תמיכה של היצרן”, כלומר הוא בד”כ לא נכתב על ידי מי שכתב את המחלקה המקורית, אלא ע”י מישהו חיצוני.

בJava החליטו שהם מממשים Extension Methods בצורה אחרת, כנראה בגלל הבעיות האלה.

איך זה עובד?

נדגים על הדוגמה של טיפ מספר 75.

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

1
2
3
4
5
6
interface Turnable
{
public void turnLeft();
public void turnOpposite();
public void turnRight();
}

אנחנו רוצים להציע מימוש דיפולטי לTurnOpposite וTurnRight ע"י TurnLeft.

בJava 8 נוכל לעשות זאת כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Turnable
{
public void turnLeft();
public void turnOpposite() default
{
for (int i = 0; i < 2; i++)
{
this.turnLeft();
}
};
public void turnRight() default
{
for (int i = 0; i < 3; i++)
{
this.turnLeft();
}
};
}

מה שקורה כאן זה שכשנממש את הממשק Turnable, נצטרך לממש רק את הפונקציה TurnLeft, ונקבל בחינם מימושים לפונקציות TurnRight וTurnOpposite.

עם זאת, קיימת לנו האופציה לדרוס את TurnRight וTurnOpposite למימושים אחרים משלנו.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Turnable
{
// Must override
public void turnLeft();
// Can override
public void turnOpposite() default
{
for (int i = 0; i < 2; i++)
{
this.turnLeft();
}
};
// Can't override
public void turnRight() final
{
for (int i = 0; i < 3; i++)
{
this.turnLeft();
}
};
}

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

הדבר הזה מאפשר להתגבר על החסרונות של Extension Methods:

נוכל להוסיף "Extension Methods" רק לממשקים שאנחנו כתבנו.

מצד אחד זו הגבלה, כיוון שלא נוכל להוסיף Extension Methods יפים לטיפוסים שלא שלנו.

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

אישית, הדבר שהכי אהבתי כאן זה העובדה שאפשר לממש Extension Method ולדרוס את המימוש הדיפולטי. זה Feature שחסר בC#, וכנראה בלתי אפשרי לעשות בגלל האופן בו מימשו Extension Methods בשפה.

המשך יום מורחב עם קפה טוב.

שתף

372. C# Annonymous class implementation

בהמשך לטיפים הקודמים, נראה כעת טכניקה לחקות את הקונספט של Anonymous Types בJava.

נניח שיש לנו ממשק כזה:

1
2
3
4
5
public interface IInvertibleConverter<TSource, TTarget>
{
TTarget ConvertFrom(TSource source);
TSource ConvertBack(TTarget target);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
public class StringIntegerConverter : IInvertibleConverter<string, int>
{
public int ConvertFrom(string source)
{
return Convert.ToInt32(source);
}
public string ConvertBack(int target)
{
return Convert.ToString(target);
}
}

במידה ונשתמש במימוש הזה במקום אחד בלבד, זה מרגיש קצת בזבוז לכתוב בשבילו מחלקה.

במקרה כזה, נוכל לעשות טריק כזה:

נממש מחלקה כזאת:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class AnonymousConverter<TSource, TTarget> : IInvertibleConverter<TSource, TTarget>
{
private readonly Func<TSource, TTarget> mConvertFrom;
private readonly Func<TTarget, TSource> mConvertBack;
public AnonymousConverter(Func<TSource, TTarget> convertFrom, Func<TTarget, TSource> convertBack)
{
mConvertFrom = convertFrom;
mConvertBack = convertBack;
}
public TTarget ConvertFrom(TSource source)
{
return mConvertFrom(source);
}
public TSource ConvertBack(TTarget target)
{
return mConvertBack(target);
}
}

היא מקבלת בConstructor שני Delegateים וקוראת להם במימושים של הפונקציות שלה.

ניצור פונקציה סטטית כזאת:

1
2
3
4
5
6
7
8
public static class InvertibleConverter
{
public static IInvertibleConverter<TSource, TTarget> Create<TSource,TTarget>
(Func<TSource, TTarget> convert, Func<TTarget,TSource> convertBack)
{
return new AnonymousConverter<TSource, TTarget>(convert, convertBack);
}
}

ואז נוכל להשתמש בה כך:

1
2
3
4
5
IInvertibleConverter<string, int> myConverter =
InvertibleConverter.Create<string, int>
(x => Convert.ToInt32(x), x => Convert.ToString(x));
string three = myConverter.ConvertBack(3);

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

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

סופ"ש אנונימי טוב.

שתף

371. Java anonymous type

בהמשך לפעם הקודמת,

נראה כעת Feature שיש בJava שאין בC#.

הFeature נקרא Anonymous types.

גם בC# קיים Feature בשם Anonymous types, אבל הוא קצת שונה (ראו גם טיפ מספר 90)

בJava הFeature מאפשר לנו לממש ממשק בתוך מתודה.

איך עושים את זה? נניח שיש לנו את הממשק הבא של Java:

1
2
3
4
interface Runnable
{
public void run();
}

נוכל לממשו כך בתוך מתודה:

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
public class MyAnnonymousExample
{
private String _member = "This is a private member";
public void test(String value)
{
Runnable runnable =
new Runnable()
{
@Override
public void run()
{
System.out.println("Hi, I just met you");
System.out.println("And this is crazy");
System.out.println("But here's your value: " + value);
System.out.println("So, call me maybe?");
System.out.println(_member);
System.out.println("It's hard to look right,");
System.out.println("At you baby");
System.out.println("but here's my member " + _member);
System.out.println("So, call me maybe?");
}
};
Thread thread = new Thread(runnable, "Carly Rae Jepsen");
thread.Start();
}
}

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

כאן בדוגמה, אנחנו רואים שהעברנו אותו לThread של Java.

שימו לב שלמחלקה הפנימית הזאת יש גישה לכל המשתנים של הפונקציה ולכל הMemberים של המחלקה, שזה ממש מגניב J

מצד שני, זה קצת מוזר – אם כבר אנחנו כותבים מספר מתודות ארוכות למחלקה, למה שלא נכתוב מחלקה רגילה וזהו?

למרות שאפשר להנות מהClosures בצורה יחסית נוחה. (כמו בAnonymous Delegates בC#, ראו גם טיפ 44)

המשך יום אנונימי עם קפה שחור טוב.

שתף