1 介绍

对于大部分对象来说,我们只需要简单地调用一个构造函数即可完成实例化,但是有一些对象的创建涉及大量的步骤,或者参数列表非常长,例如说,我们组合几个零散的字符串到一个新的字符串,或者是一个构造函数有十个参数的类,这种情况下实例化一个对象就显得不那么高效了。所以,我们需要一个解决方案来允许用户一点一点地构造起整个对象,这就是建造者模式

建造者模式就是当一个对象十分复杂需要分段构造时,我们应该提供一个接口来简单地构造这个对象。

2 使用 StringBuilder

var hello = "hello";
var sb = new StringBuilder();
sb.Append("<p>");
sb.Append(hello);
sb.Append("</p>");
Console.WriteLine(sb);

var words = new[] {"hello", "world"};
sb.Clear();
sb.Append("<ul>");
foreach (var word in words)
{
    sb.Append("\n  ");
    sb.AppendFormat("<li>{0}</li>", word);
}
sb.Append("\n");
sb.Append("</ul>");
Console.WriteLine(sb);
<p>hello</p>
<ul>
  <li>hello</li>
  <li>world</li>
</ul>

如果要构建一个 html 文本,我们使用 string就会显得很麻烦,但是使用 StringBuilder 就非常简单,StringBuilder 提供了很多方便控制格式的方法,让我们构建字符串更加简单,这就是建造者模式。

3 建造者模式

上例中的 Html 文本似乎还不够简便,所以我们在使用 StringBuilder 的基础上封装一个 HtmlElement 和 HtmlBuilder 来帮助我们快速构建 Html 文本:

public class HtmlElement
{
    public string Name { get; set; }
    public string Text { get; set; }
    public List<HtmlElement> Elements = new List<HtmlElement>();
    private const int IndentSize = 4;

    public HtmlElement()
    {
    }

    public HtmlElement(string name, string text)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
        Text = text ?? throw new ArgumentNullException(nameof(text));
    }

    private string ToStringImpl(int indent)
    {
        var sb = new StringBuilder();
        var i = new string(' ', indent * IndentSize);
        sb.AppendLine($"{i}<{Name}>");

        if (!string.IsNullOrWhiteSpace(Text))
        {
            sb.Append(new string(' ', (indent + 1) * IndentSize));
            sb.AppendLine(Text);
        }

        foreach (var element in Elements)
        {
            sb.Append(element.ToStringImpl(indent + 1));
        }
        sb.AppendLine($"{i}</{Name}>");
        return sb.ToString();
    }

    public override string ToString()
    {
        return ToStringImpl(0);
    }
}

public class HtmlBuilder
{
    private readonly string _rootName;
    private HtmlElement _root = new HtmlElement();

    public HtmlBuilder(string rootName)
    {
        _rootName = rootName;
        _root.Name = rootName;
    }

    public void AddChild(string childName, string ChildText)
    {
        var e = new HtmlElement(childName, ChildText);
        _root.Elements.Add(e);
    }

    public override string ToString()
    {
        return _root.ToString();
    }

    public void Clear()
    {
        _root = new HtmlElement {Name = _rootName};
    }
}

4 链式调用

StringBuilder 类在构建字符串时还会返回自身,也就是说 StringBuilder 支持链式调用,我们也可以为我们的 HtmlBuidler 添加支持。

public HtmlBuilder AddChild(string childName, string ChildText)
{
    var e = new HtmlElement(childName, ChildText);
    _root.Elements.Add(e);
    return this;
}

我们将void 改成 HtmlBuilder 并在执行结束时返回自身,这样我们就可以进行链式调用了: