296. Decorator and var

הכרנו בעבר את הDesign Pattern המגניב בשם Decorator (ראו גם טיפ מספר 85)

הבעיה העיקרית בDesign Pattern זה הוא שתמיד אנחנו רואים רק את הטיפוס האחרון, ואז לפעמים קשה להבין (למשל דיבוג) מאיזה טיפוס הDecorator שלנו.

אפשר לפתור בעיה זו בדרך הבאה:

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

1
2
3
4
public interface ILog
{
void Log(string content);
}

כעת יצרנו לו כמה עטיפות:

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
public class DateLogger : ILog
{
private readonly ILog mSource;
public DateLogger(ILog source)
{
mSource = source;
}
public void Log(string content)
{
mSource.Log
(string.Format("Content: {0}, Date: {1}",
content,
DateTime.Now));
}
}
public class ConsoleLogger : ILog
{
private readonly ILog mSource;
private readonly ConsoleColor mColor;
public ConsoleLogger(ILog source, ConsoleColor color)
{
mSource = source;
mColor = color;
}
public void Log(string content)
{
Console.ForegroundColor = mColor;
Console.WriteLine(content);
mSource.Log(content);
}
}

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

נוסיף פרמטר גנרי שיהיה שווה לטיפוס הקודם של הLogger:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DateLogger<TSource> : ILog
{
private readonly ILog mSource;
public DateLogger(ILog source)
{
mSource = source;
}
public void Log(string content)
{
mSource.Log
(string.Format("Content: {0}, Date: {1}",
content,
DateTime.Now));
}
}

כעת ניצור Extension Method שהופך Logger לבעל הפונקציונאליות הזאת:

1
2
3
4
5
public static DateLogger<TSource> WithDate<TSource>(this TSource source)
where TSource : ILog
{
return new DateLogger<TSource>(source);
}

נעשה אותו הדבר גם לDecorator השני ונקבל:

1
2
3
4
5
6
7
8
9
10
public class ConsoleLogger<TSource> : ILog
{
// ...
}
public static ConsoleLogger<TSource> WithConsole<TSource>(this TSource source, ConsoleColor color)
where TSource : ILog
{
return new ConsoleLogger<TSource>(source, color);
}

כעת נוכל לכתוב את הSyntax הזה:

1
2
3
4
TextLogger myLogger = new TextLogger();
ConsoleLogger<DateLogger<TextLogger>> decoratedLogger =
myLogger.WithDate().WithConsole(ConsoleColor.Red);

ככה אפשר לראות מאוד בקלות מאיזה טיפוסים מורכב הLogger שלנו.

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

1
2
var decoratedLogger =
myLogger.WithDate().WithConsole(ConsoleColor.Red);

בנוסף, אפשר לתת אפשרות להוריד פונקציונאליות מהLogger ע"י הוספת Property מתאים:

1
2
3
4
5
public interface IDecoratedLog<TSource> : ILog
where TSource : ILog
{
TSource Source { get; }
}

ואז כל מחלקה שתמממש את הממשק הזה תוכל לעשות משהו כזה:

1
DateLogger<TextLogger> source = decoratedLogger.Source;

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

סופ"ש מקושט לטובה!

שתף