به علت پیچیدگی که ممکن است در استفاده از یک ساختار composite وجود داشته باشد، builder می تواند در ساده سازی این موضوع به ما کمک کند.
یک کلاس به نام TagNode داریم که یک ساختار composite را تعریف می کند. به این صورت که لیستی از خودش را درون خودش تعریف کرده و یک ساختار درختی را شکل می دهد. بنابراین در اینجا کلاس leaf نداریم و صرفا با یک composite روبرو هستیم.
public class TagNode { private readonly StringBuilder _attributes; private readonly IList<TagNode> _children = new List<TagNode>(); private readonly string _name; private string _value = string.Empty; public TagNode(string name) { _name = name; _attributes = new StringBuilder(); } public void Add(TagNode tagNode) { _children.Add(tagNode); } public void AddAttribute(string attribute, string value) { _attributes.Append(" "); _attributes.Append(attribute); _attributes.Append("='"); _attributes.Append(value); _attributes.Append("'"); } public void AddValue(string value) { _value = value; } private StringBuilder Render(StringBuilder resultBuilder) { resultBuilder.Append($"<{this._name} {this._attributes}>"); foreach (var tagNode in _children) { tagNode.Render(resultBuilder); } resultBuilder.Append($"{this._value}"); resultBuilder.Append($"</{this._name}>"); return resultBuilder; } public string Render() { return Render(new StringBuilder()).ToString(); } }
متدهایی در این کلاس وجود دارد که از طریق آن ها می توانیم ساختار تگ را تولید کنیم.
var rootNode = new TagNode("Root"); rootNode.AddAttribute("ng-class","show"); var child = new TagNode("child"); child.AddAttribute("test-attr","test-value"); child.AddValue("test value"); rootNode.Add(child); var output = rootNode.Render();
در خط آخر، rootNode برای render شدن تمام childهای خود را هم render می کند و اگر درون childها هم تگ وجود داشته باشد آن ها نیز render می شوند.
در ادامه برای راحت بودن استفاده از این ساختار، یک builder به نام XmlDocumentBuilder ایجاد می کنیم.
public class XmlDocumentBuilder { private readonly TagNode _rootNode; private TagNode _currentParentNode; private TagNode _currentNode; public XmlDocumentBuilder(string rootNode) { _rootNode = new TagNode(rootNode); _currentNode = _rootNode; } public XmlDocumentBuilder AddChild(string name) { var child = new TagNode(name); _currentNode.Add(child); _currentParentNode = _currentNode; _currentNode = child; return this; } public XmlDocumentBuilder AddSibling(string name) { var sibling = new TagNode(name); _currentParentNode.Add(sibling); _currentNode = sibling; return this; } public XmlDocumentBuilder WithAttribute(string key, string value) { _currentNode.AddAttribute(key, value); return this; } public TagNode Build() { return _rootNode; } }
زمان صدا زدن متد Build، یک نمونه از root به دست client می رسد. در طول استفاده از این builder، با currentNode که node جاری ما می باشد کار می کنیم. متد AddChild به معنای وارد شدن یک مرحله به داخل composite است و متد AddSibling، به معنای اضافه کردن یک تگ هم سطح تگ جاری می باشد. بنابراین برای این موضوع currentParentNode را تعریف کردیم.
استفاده به این شکل می شود.
var node = new XmlDocumentBuilder("Root").WithAttribute("ng-class","show") .AddChild("child1").WithAttribute("attr-1", "1020") .AddSibling("child2").WithAttribute("attr-2","2030") .Build(); var output = node.Render();
و خروجی به این شکل به دست ما می رسد.
برای flexible کردن این ساختار می توانیم از nested builderها استفاده کنیم.
برای مثال به متد AddChild می توانیم یک builder بدهیم که خود آن می تواند دارای سطح های مختلف باشد.
var node = new TagBuilder() .AddChild(b => b.AddChild().AddChild().AddSibling()) .AddSibling( ... );
در flexible کردن این موضوع، باید به این مساله توجه داشته باشید که flexible کردن بیش از حد می تواند به عملیات ساخت آن پیچیدگی اضافه کند. بنابراین باید دقت کنید که با مساله overengineering روبرو نشوید.
کتاب Refactoring To Patterns بخش Encapsulate Composite With Builder را می توانیم به عنوان یک منبع برای این موضوع مطالعه کنید.