251. Implementing a fluent syntax part 4

ראינו בפעמים הקודמות איך מממשים Fluent Syntax.

באחת הפעמים הראשונות ראינו שבמימוש מסוים של Fluent Syntax אנחנו יוצרים מחלקה לכל ממשק, ולכן יש לנו הרבה מאוד ממשקים $ \impliedby $ יש לנו הרבה מאוד מחלקות $ \impliedby $ יש לנו הרבה מאוד קוד.

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

אחת האופציות לפתור את הבעיה היא הבאה:

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

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
public interface IQ1
{
IQ2<T> WithColumn<T>(string name);
}
public interface IQ2<T>
{
IQ2<S> WithColumn<S>(string name);
IQ3<T> MakePrimaryKey();
IQ4 WithDefaultValue(T value);
}
public interface IQ3<T>
{
IQ5 WithDefaultValue(T value);
IQ6<S> WithColumn<S>(string name);
}
public interface IQ4
{
IQ5 MakePrimaryKey();
IQ2<S> WithColumn<S>(string name);
}
public interface IQ5
{
IQ6<T> WithColumn<T>(string name);
}
public interface IQ6<T>
{
IQ5 WithDefaultValue(T value);
IQ6<S> WithColumn<S>(string name);
}

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

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
internal class TableFluentSynax : IQ1
{
#region Members
public DataTable Table { get; set; }
#endregion
#region Implementation
protected TableColumnFluentSyntax<S> AddColumn<S>(string columnName)
{
DataColumn addedColumn =
this.Table.Columns.Add(columnName, typeof(S));
return new TableColumnFluentSyntax<S>()
{
Table = this.Table,
Column = addedColumn
};
}
#endregion
#region Interface Implementation
IQ2<T> IQ1.WithColumn<T>(string name)
{
return this.AddColumn<T>(name);
}
#endregion
}
internal class TableColumnFluentSyntax<T> : TableFluentSynax, IQ2<T>, IQ3<T>, IQ4, IQ5, IQ6<T>
{
#region Members
public DataColumn Column { get; set; }
#endregion
#region Implementation
private TableColumnFluentSyntax<T> SetDefaultValue(T defaultValue)
{
this.Column.DefaultValue = defaultValue;
return this;
}
private TableColumnFluentSyntax<T> MakePrimaryKey()
{
this.Table.PrimaryKey = new[] { this.Column };
return this;
}
#endregion
#region Interface Implementation
IQ5 IQ6<T>.WithDefaultValue(T value)
{
return this.SetDefaultValue(value);
}
IQ5 IQ3<T>.WithDefaultValue(T value)
{
return this.SetDefaultValue(value);
}
IQ4 IQ2<T>.WithDefaultValue(T value)
{
return this.SetDefaultValue(value);
}
IQ5 IQ4.MakePrimaryKey()
{
return this.MakePrimaryKey();
}
IQ3<T> IQ2<T>.MakePrimaryKey()
{
return this.MakePrimaryKey();
}
IQ6<S> IQ6<T>.WithColumn<S>(string name)
{
return this.AddColumn<S>(name);
}
IQ2<S> IQ4.WithColumn<S>(string name)
{
return this.AddColumn<S>(name);
}
IQ6<S> IQ3<T>.WithColumn<S>(string name)
{
return this.AddColumn<S>(name);
}
IQ2<S> IQ2<T>.WithColumn<S>(string name)
{
return this.AddColumn<S>(name);
}
IQ6<S> IQ5.WithColumn<S>(string name)
{
return this.AddColumn<S>(name);
}
#endregion
}

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

הפונקציות האלה הן SetDefaultValue, MakePrimaryKey וAddColumn. הן מחזירות לנו Instance מתאים של המחלקה שלנו (את הInstance הנוכחי, אם אנחנו עדיין עובדים על אותה עמודה, או Instance חדש אם עברנו לעבוד על עמודה חדשה)

בנוסף, הן עושות את הפעולה שאנחנו מצפים שהן יעשו.

למה בעצם יש פה שתי מחלקות? זוכרים את הExtension Method הזה?

1
2
3
4
public static IQ1 AddTable(this DataSet dataSet, string name)
{
// ...
}

הוא מחזיר Instance של IQ1, שזה בסה"כ ממשק המציין פעולה על טבלה (כלומר, הוספת עמודה). מאחר וזה קורה לפני שעוד יש לנו עמודה ביד, זה מרגיש קצת לא נכון לממש את IQ1 כבר בTableOrColumnDefinitionSyntax<T>. למה? כי מה יהיה הפרמטר הגנרי? אפשר לשים בפרמטר הגנרי כל דבר, אבל זה מרגיש לי קצת לא נכון.

בכל מקרה, עכשיו אחרי שיש לנו את המימוש הזה, אפשר לממש בכיף את הExtension Method הזה:

1
2
3
4
5
public static IQ1 AddTable(this DataSet dataSet, string name)
{
DataTable addedTable = dataSet.Tables.Add(name);
return new TableFluentSynax() {Table = addedTable};
}

ולהשתמש בכיף בFluent Syntax שפיתחנו עד כה:

1
2
3
4
5
6
7
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(1).
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe");

וזה עובד!

שבוע צף טוב!

שתף