85. Decorator is your friend

לפעמים אנחנו יוצרים ממשק עם פונקציונאליות בסיסית

למשל

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

כעת מגיע אלינו תוכניתן חדש ונמרץ ומבקש מאיתנו שנוסיף אפשרות להוסיף תאריך.

“רק תוסיף לי Property שאומר האם לרשום תאריך או לא"

קיבלנו ממשק חדש

1
2
3
4
5
public interface ILog
{
void Log(string content);
bool WriteDate { get; set; }
}

כעת מגיע אותו תוכניתן, ומבקש שגם נכתוב לConsole את הLOG בצבע שהוא מבקש

“רק תוסיף לי עוד שני Properties שאומרים אם לכתוב לConsole ובאיזה צבע":

1
2
3
4
5
6
7
public interface ILog
{
void Log(string content);
bool WriteDate { get; set; }
bool WriteToConsole { get; set; }
ConsoleColor Color { get; set; }
}

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

פתרון אחר הוא להשתמש בDesign Pattern ידוע שנקרא Decorator.

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

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

ומשהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConsoleLogger : ILog
{
private readonly ILog m_Source;
private readonly ConsoleColor m_Color;
public ConsoleLogger(ILog source, ConsoleColor color)
{
m_Source = source;
m_Color = color;
}
public void Log(string content)
{
Console.ForegroundColor = m_Color;
Console.WriteLine(content);
m_Source.Log(content);
}
}

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

1
2
3
4
ILog baseLogger;
ILog logger = new ConsoleLogger(new DateLogger(baseLogger),
ConsoleColor.Yellow);

הרבה יותר אלגנטי מאלפי Properties.


למרחיקי לכת:

נוכל לעשות מחלקת בסיס:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class DecoratedLogger : ILog
{
private readonly ILog m_Source;
protected DecoratedLogger(ILog source)
{
m_Source = source;
}
public void Log(string content)
{
InnerLog(content);
m_Source.Log(content);
}
protected abstract void InnerLog(string content);
}

ואז רק לרשת ולממש את InnerLog.

סופ"ש ממומשק טוב

שתף