简介
生成器模式(Builder Pattern)隶属与设计模式中的创建者模式,主要目的是对复杂对象的构建步骤进行拆解。
定义
生成器模式:又名建造者模式,是将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
这个定义读起来完全不知道是啥子回事,通俗的来讲就是原来一个要构造一个完整的类,需要先 new
出这个对象来,然后对象有什么属性也需要一步步构造出来,然后再 set
到对象中,从 new
到 set
的这个过程就叫对象的建造过程,生成器模式就是将这个建造过程和后续对象的使用分离开,使得对象的建造过程可复用,建造过程也更加清晰。
角色
生成器模式从有四种角色,分别是被构造的复杂对象Product,抽象建造接口 Builder,具体建造实现 ConcreteBuilder 和指导构建过程的 Director。
这里说明一下指导构建过程的 Director, 它在正常的生成器模式里是负责组装 Builder 提供的各个部件成为 Product 的,与工厂的角色类似,但是很多时候我们并不定义这个指导者,而是由用户来担当这个角色,也就是由用户来自行决定创建过程。
模式说明
组装车、电脑这种复杂对象在很多时候是适合生成器模式的,我们这里用上节抽象工厂模式中提到的编辑器的主题来作说明。
主题通常包含一系列的复杂对象,像字体,背景,动画,菜单选项等等,其中的每一项都很复杂,复杂到也可以使用生成器模式,这里简单起见,我们就以几个属性来代替。
代码语言:javascript复制//产品 主题对象
public class Theme {
private Font font;
private Animation animation;
private Menu menu;
//setter getter 方法
//...
}
不使用生成器模式,我们要构造主题对象,需要代码里写死字体,动画等的这些项,如下:
代码语言:javascript复制public class Client {
public static void main(String[] args) {
Theme theme = new Theme();
Font font = new Font();
//... font settings
Animation = new Animation();
//... animation settings
Menu menu = new Menu();
//... menu settings
theme.setFont(font);
theme.setAnimation(animation);
theme.setMenu(menu);
// 系统切换主题
Editor.changeTheme(theme);
}
}
可以看到用户为了生成主题这个对象需要去了解字体,动画,甚至是菜单设置这一类的额外工作,根据迪米特法则,用户只需了解自己关心的知识,在这里,最核心的就是主题对象,其余的都是系统需要屏蔽的细节。
接下来我们使用生成器模式来生成这个复杂对象, 首先是抽象生成接口和具体的生成实现
代码语言:javascript复制//builder接口,一般是直接放在 Product 类里面作为静态类
public interface ThemeBuilder {
public ThemeBuilder makeTheme();
public Theme build();
public ThemeBuilder buildFont();
public ThemeBuilder buildAnimation();
public ThemeBuilder buildMenu();
// other build part
}
public class DefaultThemeBuilder {
private Theme theme;
public ThemeBuilder makeTheme() {
this.theme = new Theme();
}
public Theme build() {
return theme;
}
public ThemeBuilder buildFont() {
Font font = new Font();
// font settings
theme.setFont(font);
return this;
}
public ThemeBuilder buildAnimation() {
Animation animation = new Animation();
//animation settings
theme.setAnimation(animation);
return this;
}
public ThemeBuilder buildMenu() {
Menu menu = new Menu();
// menu set
theme.setMenu(menu);
return this;
}
}
我们将组成主题的组成部分分离开来,使用方就不需要关心具体每个部分的实现细节如何,只需要使用生成器提供的每部分的build 方法即可。
代码语言:javascript复制public class Client {
public static void main(String[] args) {
ThemeBuilder builder = new DefaultThemeBuilder();
builder.makeTheme()
.buildFont()
.buildAnimation()
.buildMenu();
Theme theme = builder.build();
// 系统切换主题
Editor.changeTheme(theme);
}
}
当用户需要使用另一个主题,只要替换 DefaultThemeBuilder
为另一个 Builder 对象即可,可以做到运行时替换。
但是还存在一个问题,即主题的构造流程一般是一致的,也就是说每次要构建一个主题对象出来,用户都得从 make 到 build 都调用一遍,针对这种情况,我们可以请出 Director 对象来帮助我们封装统一的构造过程。
代码语言:javascript复制public class ThemeDirector {
private ThemeBuilder builder;
public ThemeDirector(ThemeBuilder builder) {
this.builder = builder;
}
public void construct() {
builder.makeTheme()
.buildFont()
.buildAnimation()
.buildMenu();
}
}
public class Client {
public static void main(String[] args) {
ThemeBuilder builder = new DefaultThemeBuilder();
ThemeDirector director = new ThemeDirector(builder);
director.construct();
Theme theme = builder.build();
// 系统切换主题
Editor.changeTheme(theme);
}
}
Director 对象就跟工厂方法类似,是封装了 Theme 这个复杂对象的构建流程,不同的是 Director 是与 Builder 共同努力的,Builder 负责各个部分的构建,Director 负责将各个部分组装到产品中,而工厂方法则是把所有的细节都整合到自己的方法中来。
使用场景
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时;
- 当构造过程必须允许被构造的对象有不同的表示时。
优缺点
优点:
- 生成器模式最核心的就是分离其构造过程和组成部分,实现两者的松耦合
- 细节隐藏,产品的组成部分的构建和产品的构建分别由 Builder 和 Director 来负责,用户只需要使用两者就可以生成最终的产品
- 更好的复用,产品的构建流程可以复用,组成部分也可以复用。
缺点:
- 更复杂,理解难度更高
- 类增多
最佳实践
- 生成器模式适合于创建复杂对象,可以看到和工厂方法不同的地方在于生成器模式其实分了两层,分别是构建流程和组成对象的各个部分,以及两层之间的桥梁装配方式。因此使用哪种创建型模式取决于你对当前场景的分析
- 正常生成器模式中存在 Director 对象,用于指导构建流程,实际开发中没怎么见过 Director 对象,一般是由使用方来做装配。
- 如果生成器组成的每部分是字符串或简单对象时,Builder 可以直接实现为静态内部类,然后由用户去决定如何装配,这是更常见的一种使用方法。Java 中的
StringBuilder
就是一个没有 Director 对象的生成器模式。
public class Theme {
private String button;
private String color;
private String background;
public Theme(Builder builder) {
this.button = builder.getButton();
this.color = builder.getColor();
this.background = builder.getBackground();
}
//getter setter
public static class Builder {
private String button;
private String color;
private String background;
public Builder() {
}
public Builder button(String button) {
this.button = button;
return this;
}
public Builder color(String color) {
this.color = color;
return this;
}
public Builder background(String background) {
this.background = background;
return this;
}
public Theme build() {
return new Theme(this);
}
}
}
用户直接使用Builder 来自行装配
代码语言:javascript复制public class Client {
public static void main(String[] args) {
Theme.Builder builder = new Theme.Builder();
Theme theme = builder.button("button")
.color("white")
.background("black")
.build();
// 系统切换主题
Editor.changeTheme(theme);
}
}