菜狗U3D程序猿的经验积累——建造者模式

209 阅读8分钟

意图

建造者是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式对象。

问题

假设有这样一个复杂对象,在对其进行构造时需要对诸多变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数让人基本看不懂的构造函数中;甚至还有更糟糕的情况,那就是这些代码散落在客户端的多个位置。

例如,我们来思考如何创建一个房屋对象,建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后在建造一个屋顶。但是如果你想要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。但是如果你想要一栋更宽敞更明亮的房屋,还要院子和其它设施,那又该怎么办呢?

最简单的方法是扩展房屋基类,然后创建一系列涵盖所有参数组合的子类。但最终你将面对相当数量的子类。任何新增的参数都会使这个层次结构更加复杂。

另一种方法则无需生成子类,你可以在房屋基类中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题。

通常情况下,绝大部分的参数都没使用,这使得对于构造函数的调用十分不简洁,例如,只有很少的房子有游泳池,因此与游泳池相关的参数十之八九是毫无用处的。

解决方法

建造者模式建议将对象构造代码从产生类中抽取出来,并将其放在一个名为建造者的独立对象中。

该模式会将对象构造过程划分为一组步骤,比如创建墙壁和创建房门等。每次创建对象时,你都需要通过建造者对象执行一些列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。

在这种情况下,你可以创建多个不同的建造者,用不同方式实现一组相同的创建步骤。然后你就可以在创建中使用这些建造者来生成不同类型的对象。

例如,假设第一个建造者使用木头和玻璃制造房屋,第二个建造者使用石头和钢铁,而第三个建造者使用黄金和钻石。在调用同一组步骤后,第一个建造者会给你一栋房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。但是,只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时,这样的调用才能返回需要的房屋。

主管

你可以进一步将用于创建产品的一系列建造者步骤调用调用抽取成为单独的主管类。主管类可定义创建步骤的执行顺序,而建造者则提供这些步骤的实现。

严格来说,你的程序中并不一定需要主管类。客户端代码可直接以待定顺序调用创建步骤。不过,主管类中非常适合放入各种例行构造流程,以便在程序中反复使用。

此外,对客户端代码来说,主管类完全隐藏了产品构造细节。客户端只需要将一个建造者与主管类关联,然后使用主管类来构造产品,就能从建造者处获得构造结构了。

建造者模式适应场景

使用建造者模式可避免“重叠构造函数”的出现。

假设你的构造函数中有是个可选参数,那么调用该函数会非常不方便;因此,你需要中在这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需要调用主构造函数,传递一些默认数值来替代省略掉参数。

建造者模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。

当你希望使用代码创建不用形式的产品时,可使用建造者模式。

如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用建造者模式。

基本建造者接口中定义了所有可能的制造步骤,具体建造者将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。

使用建造者构造组合数或其他复杂对象。

建造者模式让你能分步骤构造产品,你可以延迟执行某些步骤而不会影响最终产品。你甚至可以递归调用这些步骤,这在创建对象树时非常方便。

建造者在执行制造步骤时,不能对外发布未完成的产品,这样可以避免客户端代码获取到不完整结果对象的情况。

实现方法

1.清晰地定义通用步骤,确保它们可以制造所有形式地产品,否则你将无法进一步实施该模式。

2.在基本建造者接口中声明这写步骤。

3.为每个形式地产品创建具体建造者类,并实现其构造步骤。

不要忘记实现获取构造结果对象地方法。你不能在建造者接口中声明该方法,因为不同建造者构造的产品可能没有公共接口,因此你就不知道该方法返回的对象类型。但是,如果所有产品都位于单一类层次中,你就可以安全的在基本接口中添加获取生成对象的方法。

4.考虑创建主管类。它可以使用同一建造者对象来封装多种构造产品的方式。

5。客户端代码会同时创建建造者和主管对象。构造开始前,客户端必须将建造者对象传递给主管对象。通常情况下,客户端只需调用主管类构造函数即可。主管类使用建造者对象完成后续所有制造任务。还有另一种方式,那就是客户端可以将建造者对象直接传递给主管类的制造方法。

6.只有在所有产品都遵循相同接口的情况下,构造结构可以直接通过主管类获取。否则,客户端应当通过生成器获取构造结果。

生成器的优缺点

优点:你可以分布创建对象,暂缓创建步骤或递归运行创建步骤

生成不同形式的产品时,你可以复用相同的制造代码。

单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点: 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。

与其他模式的关系

·在许多设计工作的初期都会使用工厂方法模式,随后烟花为使用抽象工厂模式、原型模式或建造者模式。

·建造者重点关注如何分布生成复杂对象。抽象工厂专门用生产一系列相关对象。抽象工厂会马上返回产品,建造者则允许你在获取产品前执行一些额外构造步骤。

·你可以创建复杂组合模式树时使用建造者,因为这可使其构造步骤以递归的方式运行。

·你可以结合建造者和桥接模式:主管类负责抽象工作,各种不同的建造者负责实现工作。

·抽象工厂、建造者和原型都可以使用单例模式来实现。

using System;
using  System.Collections.Generic;


public interface IBuilder
{
 void BuildPartA();
 void BuildPartB();
 void BuildPartC();
}

public class ConcreteBuilder: IBuilder
{
 private Product _product = new Product();
 public ConcreteBuilder()
 {
  this.Reset();
 }
 public void Reset()
 {
  this._product= new Product();
 }
 public void BuildPartA()
 {
  this._product.Add("PartA1");
 }
 public void BuildPartB()
 {
  this._product.Add("PartB1");
 }
 public void BuildPartC()
 {
  this._product.Add("PartC1");
 }
 public Product GetProduct()
 {
  Product result =this._product;
  this.Reset();
  return result;
 }
}
public class Product
{
 private List<object> _parts =new List<object>();
 public void Add(string part)
 {
  this._parts.Add(part);
 }
 public string ListParts()
 {
  string str =string.Empty;
  for(int i=0;i<this._parts.Count;i++)
  {
   str +=this._parts[i]+",";
  }
  str =str.Remove(str.Length-2);
  return "Product parts:"+str+"\n";
 }
}
public class Director
{
 private IBuild _builder;
 public IBuild Builder
 {
  set{_builder=value;}
 }
 public void buildMinimalviableProduct()
 {
  this._builder.BuildPartA();
 }
 public void buildFullFeaturedProduct()
 {
  this._builder.BuildPartA();
  this._builder.BuildPartB();
  this._builder.BuildPartC();
 }
}