设计模式(四)—— 建造者模式

模式简介


将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

结构说明


UML类图

角色说明

  • Builder

为创建一个Product对象的各个部件指定抽象接口

  • ConcreteBuilder

具体的生成器,实现Builder的接口以构造和装配该产品的各个部件

  • Director

导向器,构造一个使用Builder接口的对象

  • Product

被构建的复杂对象,ConcreteBuilder创建该产品的内部表示并定义它的装配过程

UML时序图

示例分析


文档转换器

功能简介:文档转换器读取一份Text文档,并根据文档中的内容为不同部门生成指定格式报告。

分析:虽然各部门指定的报告格式及文件类型不同,但是文档创建的顺序均可以理解为:设置文档名->设置文档类型->填写标题->填写报告内容

设计思路

创建抽象建造者ReportBuilder

abstract class ReportBuilder
{
    public abstract void SetReportName();
    public abstract void SetReportType();
    public abstract void FillTitle();
    public abstract void FillContent();
}

创建产品类TextReport和ExcelReport

class TextReport
{
    public string ReportName { get; set; }
    public string ReportType { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public void Show()
    {
        Console.WriteLine($"ReportName:{ReportName}");
        Console.WriteLine($"ReportType:{ReportType}");
        Console.WriteLine($"Title:[{Title}]");
        Console.WriteLine($"Content:{Content}");
    }
}

class ExcelReport
{
    public string ReportName { get; set; }
    public string ReportType { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public void Show()
    {
        Console.WriteLine($"--ReportName:{ReportName}--");
        Console.WriteLine("----------------------------");
        Console.WriteLine($"--ReportType:{ReportType}--");
        Console.WriteLine("----------------------------");
        Console.WriteLine($"--Title:[{Title}]--");
        Console.WriteLine("----------------------------");
        Console.WriteLine($"--Content:{Content}--");
    }
}

分别实现具体建造者TextReportBuilder和ExcelReportBuilder

class TextReportBuilder : ReportBuilder
{
    TextReport report = new TextReport();
    public override void FillContent()
    {
        report.Content = "TEXT REPORT CONTENT";
    }

    public override void FillTitle()
    {
        report.Title = "This is a TEXT REPORT";
    }

    public override void SetReportName()
    {
        report.ReportName = "TextReport";
    }

    public override void SetReportType()
    {
        report.ReportType = "txt";
    }

    public TextReport GetResult()
    {
        return report;
    }
}

class ExcelReportBuilder : ReportBuilder
{
    ExcelReport report = new ExcelReport();
    public override void FillContent()
    {
        report.Content = "EXCEL REPORT CONTENT";
    }

    public override void FillTitle()
    {
        report.Title = "This is a EXCEL REPORT";
    }

    public override void SetReportName()
    {
        report.ReportName = "TextReport";
    }

    public override void SetReportType()
    {
        report.ReportType = "txt";
    }

    public ExcelReport GetResult()
    {
        return report;
    }
}

声明指挥着TextReader

class TextReader
{
    private string text;
    public TextReader(string text)
    {
        this.text = text;
    }

    public void Construct(ReportBuilder reportBuilder)
    {
        reportBuilder.SetReportName();
        reportBuilder.SetReportType();
        reportBuilder.FillTitle();
        reportBuilder.FillContent();
    }

}

客户端调用

TextReader reader = new TextReader("Text Content");
ExcelReportBuilder builder = new ExcelReportBuilder();
reader.Construct(builder);
builder.GetResult().Show();
Console.ReadLine();

建造者模式扩展


想想我们怎样通过SMTP发送邮件,大致代码如下:

SmtpClient mailClient = newSmtpClient(host);
mailClient.Credentials = CredentialCache.DefaultNetworkCredentials;
MailMessage msg = new MailMessage();
msg.From = new MailAddress(address);
msg.To.Add(to);
msg.CC.Add(cc);
msg.Attachments.Add(attachment);
msg.Subject = subject;
msg.SubjectEncoding = Encoding.UTF8;
msg.Body = body;
msg.BodyEncoding = Encoding.UTF8;
msg.IsBodyHtml = false;
msg.Priority = priority;
mailClient.Send(msg);

对于各个公司来说,邮件服务器信息一般是固定的,所以发送邮件程序里变化部分仅仅是邮件内容。

使用建造者模式封装一个邮件构造器

public class MailBuilder
{
    private string host = "xxx.xxx.com";
    private SmtpClient client;
    private MailMessage msg;
    public MailBuilder()
    {
        client = new SmtpClient(host);
        client.Credentials = CredentialCache.DefaultNetworkCredentials;
        msg = new MailMessage();
    }

    public MailBuilder Subject(string subject)
    {
        msg.Subject = subject;
        return this;
    }

    public MailBuilder Body(string body)
    {
        msg.Body = body;
        return this;
    }

    public MailBuilder IsBodyHtml(bool isBodyHtml)
    {
        msg.IsBodyHtml = isBodyHtml;
        return this;
    }

    public MailBuilder MailFrom(string mailFrom)
    {
        msg.From = new MailAddress(mailFrom);
        return this;
    }

    public MailBuilder MailTo(List<string> mailTo)
    {
        mailTo.ForEach(p => msg.To.Add(p));
        return this;
    }

    public MailBuilder MailCc(List<string> mailCc)
    {
        mailCc.ForEach(p => msg.CC.Add(p));
        return this;
    }

    public MailBuilder Attachment(List<Attachment> attachment)
    {
        attachment.ForEach(p => msg.Attachments.Add(p));
        return this;
    }

    public MailBuilder MailPriority(MailPriority mailpriority)
    {
        msg.Priority = mailpriority;
        return this;
    }

    public void Send()
    {
        client.Send(msg);
    }
}

客户端调用:

MailBuilder mail = new MailBuilder();
mail.Subject($"{DateTime.Now}")
    .Body("Test")
    .MailFrom("xxxx@xxx.com")
    .MailTo(to)
    .Send();
    

如此调用,是不是优雅了很多呢?

注意:这里省略了抽象建造者以及指挥者,Builder角色扮演了指挥者与建造者双重角色

建造者模式与抽象工厂模式比较


  • 建造者模式侧重产品构造的过程及顺序,

  • 建造者模式强调返回一个组装好的完整的产品,抽象工厂模式生产一系列产品。

优缺点


优点

  • 将产品的构建与表示解耦,使得相同的创建过程可以复用在不同产品上

  • 具体建造者相互独立,替换起来比较方便,用户使用不同建造者即可得到不同的产品对象
  • 对产品的创建过程加以控制,使得创建过程更加清晰
  • 增加具体建造者无需修改现有代码,便于扩展

缺点

  • 仅适用于产品组成与创建过程相似的情况
  • 随着建造者数量的增加,导致系统变得很庞大

使用场景


  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时

  • 但个构造过程必须允许被构造的对象有不同的表示时

posted @ 2018-05-11 13:18 Answer.Geng 阅读(...) 评论(...) 编辑 收藏