关于建造者模式(或者又叫构造者模式),我在网上看了很多文章。其中不乏很多人直接把建造者模式等同于builder构造器。我想说这是两种完全不同的层次和方向,一种是从系统设计层面考虑的设计模式,用于使整个系统构件间解耦更加明显,更易于扩展;另一种则是为了让构造器能适应多个参数而出现的构造函数变体。
这里不再对网上漫天的Builder构造器进行解释,只针对建造者模式进行讨论。
建造者模式:将类的使用者与类的建造方式解耦,将类的使用者与类的建造者解耦。
当然,上面这句话也是我个人对建造者模式的理解。建造者模式就是实现了三点之间的解耦:类的使用者、类的建造者、类的建造方式。
我们先来看一下建造者模式的示意类图:
- Builder, 抽象建造者,定义了建造一个类(Product)需要实现的方法;
- ConcreteBuilder,具体建造者,实现了抽象建造者定义的方法;
- Product,产品,其实就是建造者需要建造的东西;
- Director,其实它才是真正的类的建造者,上面所说的Builder,其实只是定义了类的建造方式,但是真正和类的使用者交互的是Director,它作为具体类的提供者,完成了建造的步骤。
OK, 我们现在来从上面说的两个解耦进行后续的解释。
将类的使用者与类的建造方式解耦
平常我们想进行一个类的构造会怎么写?一般来说我们先new一个实例出来,然后对各个变量进行set,最后做我们想做的事情。这里大家也可以换成直接在构造函数里面传入参数,大致意思是一样的。
Foo foo = new Foo();
foo.setA(123);
foo.setB("abc");
foo.setC(new bar());
foo.doSomething();
但是这种做法,实际上将类的初始化和各项配置工作都移交给了类的使用者来做,大大增强了类的使用者和类的建造方式之间的耦合关系,类的使用者必须完全了解如何建造这个类才可以,否则便等不到一个功能完好的实例,更不要说正确地使用了。
OK,那如果说我现在有这样一个产品类,会被别人使用到。我们先假设产品类是这个样子:
public class Product {
private String name;
private BigDecimal price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
而我们存在多种建造这个产品的方式,但首先我们需要定义一个接口,用以确定产品的建造者需要做哪些工作:
public interface IProductBuilder {
public void giveName();
public void givePrice();
public Product build();
}
而后我们有了两个建造商品的类,一个生产高质量商品,另一个生产普通商品:
public class HighQualityProductBuilder implements IProductBuilder{
private Product product = new Product();;
@Override
public void giveName() {
product.setName("A High Quality Product comes out!");
}
@Override
public void givePrice() {
product.setPrice(new BigDecimal("299.99"));
}
@Override
public Product build() {
giveName();
givePrice();
refine();
return product;
}
private void refine(){
System.out.println("Don't know how, but the product get refined!");
}
}
public class NormalProductBuilder implements IProductBuilder {
private Product product = new Product();;
@Override
public void giveName() {
product.setName("Product with normal quality......");
}
@Override
public void givePrice() {
product.setPrice(new BigDecimal("1.59"));
}
@Override
public Product build() {
giveName();
givePrice();
return product;
}
}
这个时候我们能够看到,在Builder类中,我已经将值赋给了product的域,并且在build()方法中实现了产品的装配和建造过程。后面会提到,这其实是建造者模式的一大问题所在。
而后,我们在main方法中模拟使用该类,这样其实就能够实现了将类的使用者与类的建造方式解耦这一目的:
public static void main(String[] args){
IProductBuilder builder = new HighQualityProductBuilder();
Product product = builder.build();
}
但是到此为止,我们做得还不够。大家可以看到,在类图中展示的Builder、ConcreteBuilder、和Product都已经出现了,但是Director还迟迟不肯露面。下面我们就来说一下Director在整个建造者模式中的作用。
将类的使用者与类的建造者解耦
为了实现这一解耦目标,我们引入了Director这个参与者。它主要做的工作,就是封装了Builder,然后直接和外界类的使用者进行交互:
public class Director {
private IProductBuilder builder;
public Director(IProductBuilder builder){
this.builder = builder;
}
public Product construct(){
return builder.build();
}
}
而后我们在main方法中使用director来替代原来直接写builder的方式:
public static void main(String[] args){
Director director = new Director(new NormalProductBuilder());
Product product = director.construct();
}
看到这里,很多人可能会骂我根本就不懂。这不还是写死了NormalProductBuilder了么?不照样是紧耦合了么?有什么意义?其实如果大家写过一点Spring就能顿悟这里面的妙处,只要我在Director构造时使用注入,并将其实现为一个Bean,就可以很轻松地将其进行解耦。由一个Director来作为选择和管理Builder的入口,而通过注入的方式将使用者与后方隔离开来,这才是建造者模式真正强大的地方。
一大问题
另外我上面也说了,建造者模式的一大问题,就是它把类的建造过程写死了,绑死了,写成了一套流程化的代码逻辑,不易更改。
所以说,建造者模式适用于不易变更的且构建复杂的类的实例化场景,而对那些生成过程经常改变的类,则不建议使用该模式,因为得到的收益可能远比不上修改的成本。