轉自:《模式——工程化實現及擴充》(設計模式C# 版)
http://www.cnblogs.com/callwangxiang/
http://www.cnblogs.com/callwangxiang/archive/2011/05/31/ExerciseAAFluentInterface.html的參考答案
參考答案
設計要點:
- 採用連貫介面設計表格的建立過程
- 由於表格Head涉及一層嵌套、Body涉及兩層嵌套,因此為了便於調整和修改,每個節點元素類型都要保留回溯到父節點的引用
1、設計具有Fluent特徵的抽象節點類型
/// <summary>/// 修改後具有Fluent特徵的集合類型/// </summary>/// <typeparam name="T">集合元素類型</typeparam>/// <typeparam name="TParent">父節點類型</typeparam>class FluentCollection<TElement, TParent> where TElement : class where TParent : class { protected List<TElement> list = new List<TElement>(); TParent parent; public FluentCollection(TParent parent) { if(parent == null) throw new ArgumentNullException("parent"); this.parent = parent; } /// <summary> /// 返回父節點 /// </summary> public TParent Parent{get{ return parent;}} /// <summary> /// 如何獲得一個TElement類型執行個體的委託 /// </summary> public Func<TElement> GetInstance { get; set; } /// <summary> /// 具有fluent特徵的追加操作 /// </summary> /// <param name="t"></param> /// <returns></returns> public FluentCollection<TElement, TParent> Add(TElement t) { list.Add(t); return this; } /// <summary> /// 具有fluent特徵的空置操作 /// </summary> /// <returns></returns> public FluentCollection<TElement, TParent> Skip { get { list.Add(GetInstance()); return this; } } /// <summary> /// 執行LINQ的foreach操作 /// </summary> /// <param name="action"></param> public void ForEach(Action<TElement> action) { list.ForEach(action); }}/// <summary>/// 父節點為table的元素/// </summary>class WithTableObject{ Table table; // 父節點 public WithTableObject(Table table) { if(table == null) throw new ArgumentNullException("table"); this.table = table; } /// <summary> /// 指向父節點——table /// </summary> public Table Parent{get{ return table;}}}
2、定義各個節點類型
class Notation{ public Notation(){Data = string.Empty;} public Notation(string data) {Data = data; } public string Data { get; private set; }}/// <summary>/// n元素/// </summary>class Item : Notation{ public Item():base(){} public Item(string data) : base(data){}}/// <summary>/// col 元素/// </summary>class Column : Notation{ public Column():base(){} public Column(string data) : base(data) { }}/// <summary>/// line 元素 /// </summary>class Line{ FluentCollection<Item, Line> items; Body body; public Line(Body body) { if(body == null) throw new ArgumentNullException("body"); this.body = body; items = new FluentCollection<Item, Line>(this) { GetInstance = () => { return new Item(); } }; } /// <summary> /// 父節點 /// </summary> public Body Body { get { return body; } } public FluentCollection<Item, Line> Items { get { return items; } } public Line NewLine{get{return body.NewLine;}}}/// <summary>/// body 元素/// </summary>class Body : WithTableObject{ List<Line> lines = new List<Line>(); public Body(Table table) : base(table){} public Line NewLine { get { var line = new Line(this); lines.Add(line); return line; } } public List<Line> Lines { get { return lines;}}} /// <summary>/// head 元素/// </summary>class Head : WithTableObject{ FluentCollection<Column, Head> columns; public Head(Table table) : base(table) { columns = new FluentCollection<Column, Head>(this) { GetInstance = () => { return new Column(); } }; } public FluentCollection<Column, Head> Columns { get { return columns; } }} class Table{ string name; Body body; Head head; public Table() { body = new Body(this); head = new Head(this); } public Table Name(string name) { if(string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); this.name = name; return this; } public override string ToString(){return name;} public Body Body{get{ return body;}} public Head Head{get{ return head;}}}
3、定義產生試算表的資料類型
class Notation{ public Notation(){Data = string.Empty;} public Notation(string data) {Data = data; } public string Data { get; private set; }}/// <summary>/// n元素/// </summary>class Item : Notation{ public Item():base(){} public Item(string data) : base(data){}}/// <summary>/// col 元素/// </summary>class Column : Notation{ public Column():base(){} public Column(string data) : base(data) { }}/// <summary>/// line 元素 /// </summary>class Line{ FluentCollection<Item, Line> items; Body body; public Line(Body body) { if(body == null) throw new ArgumentNullException("body"); this.body = body; items = new FluentCollection<Item, Line>(this) { GetInstance = () => { return new Item(); } }; } /// <summary> /// 父節點 /// </summary> public Body Body { get { return body; } } public FluentCollection<Item, Line> Items { get { return items; } } public Line NewLine{get{return body.NewLine;}}}/// <summary>/// body 元素/// </summary>class Body : WithTableObject{ List<Line> lines = new List<Line>(); public Body(Table table) : base(table){} public Line NewLine { get { var line = new Line(this); lines.Add(line); return line; } } public List<Line> Lines { get { return lines;}}} /// <summary>/// head 元素/// </summary>class Head : WithTableObject{ FluentCollection<Column, Head> columns; public Head(Table table) : base(table) { columns = new FluentCollection<Column, Head>(this) { GetInstance = () => { return new Column(); } }; } public FluentCollection<Column, Head> Columns { get { return columns; } }} class Table{ string name; Body body; Head head; public Table() { body = new Body(this); head = new Head(this); } public Table Name(string name) { if(string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); this.name = name; return this; } public override string ToString(){return name;} public Body Body{get{ return body;}} public Head Head{get{ return head;}}}
4、單元測試
[TestClass]public class FluentInterfaceFixture{ TableWriter writer; [TestInitialize] public void Initialize() { writer = new TableWriter(); } [TestMethod] public void TestFullFillTable() { writer.Output( new Table() .Name("full fill") .Head .Columns .Add(new Column("first")) .Add(new Column("second")) .Add(new Column("thrid")) .Parent .Parent .Body .NewLine.Items.Add(new Item("11")).Add(new Item("12")).Add(new Item("13")).Parent .NewLine.Items.Add(new Item("21")).Add(new Item("22")).Add(new Item("23")).Parent .Body .Parent ); } [TestMethod] public void TestSkipColumnTable() { writer.Output( new Table() .Name("skip columns") .Head .Columns .Add(new Column("first")) .Skip .Add(new Column("thrid")) .Parent .Parent .Body .NewLine.Items.Add(new Item("11")).Add(new Item("12")).Add(new Item("13")).Parent .NewLine.Items.Add(new Item("21")).Add(new Item("22")).Add(new Item("23")).Parent .Body .Parent ); } [TestMethod] public void TestSkiItemsTable() { writer.Output( new Table() .Name("skip items") .Head .Columns .Add(new Column("first")) .Add(new Column("second")) .Add(new Column("thrid")) .Parent .Parent .Body .NewLine.Items.Add(new Item("11")).Skip.Add(new Item("13")).Parent .NewLine.Items.Add(new Item("21")).Add(new Item("22")).Skip.Parent .Body .Parent ); } [TestMethod] public void TestSkipColumnsAndItemsTable() { writer.Output( new Table() .Name("skip columns and items") .Head .Columns .Add(new Column("first")) .Skip .Add(new Column("thrid")) .Parent .Parent .Body .NewLine.Items.Add(new Item("11")).Skip.Add(new Item("13")).Parent .NewLine.Items.Add(new Item("21")).Add(new Item("22")).Skip.Parent .Body .Parent ); }}
5、測試結果
------ Test started: Assembly: Concept.Tests.dll ------<table> <name>full fill</name> <head><col>first</col><col>second</col><col>thrid</col> </head> <body> <line> <item><n>11</n><n>12</n><n>13</n> </item> <item><n>21</n><n>22</n><n>23</n> </item> </line> </body></table><table> <name>skip columns</name> <head><col>first</col><col></col><col>thrid</col> </head> <body> <line> <item><n>11</n><n>12</n><n>13</n> </item> <item><n>21</n><n>22</n><n>23</n> </item> </line> </body></table><table> <name>skip items</name> <head><col>first</col><col>second</col><col>thrid</col> </head> <body> <line> <item><n>11</n><n></n><n>13</n> </item> <item><n>21</n><n>22</n><n></n> </item> </line> </body></table><table> <name>skip columns and items</name> <head><col>first</col><col></col><col>thrid</col> </head> <body> <line> <item><n>11</n><n></n><n>13</n> </item> <item><n>21</n><n>22</n><n></n> </item> </line> </body></table>4 passed, 0 failed, 0 skipped, took 1.29 seconds (MSTest 10.0).
測試確認Fluent介面的有效性