对于建造者这个设计模式平时很常见,举个例子Android中的AlertDialog,这个在平时很常用。一段代码如下。
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("title").setMessage("message").setPositiveButton("ok", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).setNegativeButton("cancel", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
这里使用了Builder方法来构建,set系列函数来设置数据,build来构建返回对象。
常见的建造者模式都是如此的形式,而构建者模式也并没有什么特别之处,现在比较常见的有两种表现形式,其中一种是最原始的构建者模式,另一种是实际工程中常见的设计模式,下面先说下后面一种。
形式1
这里通过一个栗子来说这种模式。
下面是一个App的构建过程,其中分为几个部分:文档,设计,编码,测试,部署这几个部分。具体的解释我写到注释里。
public class App {
/**
* 写文档
*/
private String doc;
/**
* 做设计
*/
private String design;
/**
* 编码
*/
private String coding;
/**
* 测试
*/
private String test;
/**
* 部署
*/
private String deploy;
//构造方法
public App(String doc, String design, String coding, String test, String deploy) {
this.doc = doc;
this.design = design;
this.coding = coding;
this.test = test;
this.deploy = deploy;
}
public App() {
}
//提供一个传入构造器的方法,调用build方法才把属性赋值,这里可以实现一个延迟构建的功能
public App(Builder builder) {
this.doc = builder.doc;
this.design = builder.design;
this.coding = builder.coding;
this.test = builder.test;
this.deploy = builder.deploy;
}
public String getDoc() {
return doc;
}
public void setDoc(String doc) {
this.doc = doc;
}
public String getDesign() {
return design;
}
public void setDesign(String design) {
this.design = design;
}
public String getCoding() {
return coding;
}
public void setCoding(String coding) {
this.coding = coding;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
public String getDeploy() {
return deploy;
}
public void setDeploy(String deploy) {
this.deploy = deploy;
}
//静态内部类,构造器
public static final class Builder {
private String doc;
private String design;
private String coding;
private String test;
private String deploy;
//一般使用这样的构造方法,当然也支持传参
public Builder() {
}
//构造器属性的初始化,同时return this支持链式调用
public Builder doc(String doc) {
this.doc = doc;
return this;
}
public Builder design(String design) {
this.design = design;
return this;
}
public Builder coding(String coding) {
this.coding = coding;
return this;
}
public Builder test(String test) {
this.test = test;
return this;
}
public Builder deploy(String deploy) {
this.deploy = deploy;
return this;
}
//build的时候才开始创建App
public App build() {
return new App(this);
}
}
}
要是不使用设计模式,我们就可以这么来写:
App app = new App("doc","design","coding","test","deploy");
说实话现在写久了反倒觉得new关键字没啥用,如果能像Dart一样就好了,初始化一个App的话,除非提供大量的构造方法重载,否则基本上不可能一行就实现一个可选参数的"App",正常的set方法又太麻烦(不支持链式调用的。
而使用设计模式,就变成了这样。
可选择需要配置的项目,且配置后对象还没有创建,直到我们build的时候才将对象构建出来。
App.Builder builder = new App.Builder()
.doc("doc").design("design").coding("coding").test("test").deploy("deploy");
App app2 = builder.build();
这个场景在我们有大量的属性的时候很方便。
这时候你就会有疑问了,仅仅是把初始化的工作套了一层,好像也没什么卵用,费时费力而且setter方法也可以支持链式调用啊,延迟加载也没啥用并且Builder的创建又会浪费内存,那么构建者模式存在的意义是什么呢?
栗子
下面是几个提出的问题。
- 举这样一个例子,一个App,可能类型不同,实现的方案不同,比如说抖音要在设计上多花功夫,王者荣耀要在编码性能上下功夫,这就要求对design() 方法,对coding() 方法做特殊处理,这里就出现了2个构建者。
- 我们写一个实体,一般包括属性,get、set方法,而且这些方法中一般不会做很多逻辑处理,那么逻辑处理交给谁?让调用方处理肯定是不合理的。这个时候构建者的主角光环就出来了,很多错误可以直接在构建之前发现。
- 类似于上个问题,如果我要严格约束调用顺序,doc一定在design之前,test一定要在coding之后如何操作? 约束限制写在set里面是绝对不合理的,跟上面一条类似。
上面的几个问题已经能够说明构建者模式还是有存在的意思的,但实际上上面这个只是一个简化版的构建者模式,其中Builder扮演者构建者和指挥者的两重角色。
形式2
解决栗子一节的问题1,如果有不同的构建要求怎么办?那么只能说写两个构建者了。先来改造我们的代码。
public class App {
/**
* 写文档
*/
private String doc;
/**
* 做设计
*/
private String design;
/**
* 编码
*/
private String coding;
/**
* 测试
*/
private String test;
/**
* 部署
*/
private String deploy;
public App(String doc, String design, String coding, String test, String deploy) {
this.doc = doc;
this.design = design;
this.coding = coding;
this.test = test;
this.deploy = deploy;
}
public App(ByteDanceBuilder builder) {
this.doc = builder.doc;
this.design = builder.design;
this.coding = builder.coding;
this.test = builder.test;
this.deploy = builder.deploy;
}
public App(TimiBuilder builder){
this.doc = builder.doc;
this.design = builder.design;
this.coding = builder.coding;
this.test = builder.test;
this.deploy = builder.deploy;
}
//字节跳动构建者
public static final class ByteDanceBuilder {
private String doc;
private String design;
private String coding;
private String test;
private String deploy;
public ByteDanceBuilder doc(String doc) {
this.doc = doc;
return this;
}
public ByteDanceBuilder design(String design) {
this.design = design;
//if design 不合格 throw 异常
return this;
}
public ByteDanceBuilder coding(String coding) {
this.coding = coding;
return this;
}
public ByteDanceBuilder test(String test) {
this.test = test;
return this;
}
public ByteDanceBuilder deploy(String deploy) {
this.deploy = deploy;
return this;
}
public App build() {
return new App(this);
}
}
//天美工作室构建者
public static final class TimiBuilder{
private String doc;
private String design;
private String coding;
private String test;
private String deploy;
public TimiBuilder doc(String doc) {
this.doc = doc;
return this;
}
public TimiBuilder design(String design) {
this.design = design;
return this;
}
public TimiBuilder coding(String coding) {
this.coding = coding;
//if coding 不合格 throw 异常
return this;
}
public TimiBuilder test(String test) {
this.test = test;
return this;
}
public TimiBuilder deploy(String deploy) {
this.deploy = deploy;
return this;
}
public App build() {
return new App(this);
}
}
}
上面我省略了一些代码,留下关键的和新加的,可以看到代码里已经有两个构建者了,一个是抖音App的字节跳动构建者,另一个是王者荣耀的天美工作室的构建者,而且他们分别对设计和代码质量严格把关。这样App类就要提供两个构造方法来实现构建过程。
使用如下:
App.ByteDanceBuilder builder1 = new App.ByteDanceBuilder()
.doc("doc").design("pretty design").coding("coding").test("test").deploy("deploy");
App app1 = builder1.build();
App.TimiBuilder builder2 = new App.TimiBuilder()
.doc("doc").design("design").coding("pretty coding").test("test").deploy("deploy");
App app2 = builder2.build();
你把改造完的代码提交了,然后后来老板又提出我们的App需要加一个运营的步骤,在部署之后。然后你又屁颠儿的去修改每一个构建者的结构,万一写错了,那岂不是凉凉……于是定义了一个接口约束。
Builder
public interface Builder {
Builder doc(String doc);
Builder design(String design);
Builder coding(String coding);
Builder test(String test);
Builder deploy(String deploy);
Builder operating(String operating);
App build();
}
ByteDanceBuilder
public class ByteDanceBuilder implements Builder {
private App app = new App();
@Override
public Builder doc(String doc) {
app.setDoc(doc);
return this;
}
@Override
public Builder design(String design) {
app.setDesign(design);
return this;
}
@Override
public Builder coding(String coding) {
app.setCoding(coding);
return this;
}
@Override
public Builder test(String test) {
app.setTest(test);
return this;
}
@Override
public Builder deploy(String deploy) {
app.setDeploy(deploy);
return this;
}
@Override
public Builder operating(String operating) {
app.setOperating(operating);
return this;
}
@Override
public App build() {
return app;
}
}
然后修改App类为默认的样子,就是仅有属性和get/set方法。
使用如下:
ByteDanceBuilder builder = new ByteDanceBuilder();
App app = builder.doc("doc").design("design").coding("coding").test("test").deploy("deploy").operating("").build();
可以看到,代码上不一样了,但是使用差不多,要是构建者种类比较多的话,我们要写n多的构造者类,n多种链式调用。
![image.png](https://img.haomeiwen.com/i2441545/5056e33de4800983.png&originHeight=222&originWidth=227&size=51833&status=done&style=none&width=263?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
降级款
还有一个很累赘的东西,在这里加上他还不如不加,就是前面有提到过依据的指挥者,指挥者的话对构建者进行了一个管理,说起来很像一个管理员。
public class Director {
public void build(Builder builder) {
builder.doc("doc");
builder.design("design");
builder.coding("coding");
builder.test("test");
builder.deploy("deploy");
builder.operating("");
}
}
在管理者中执行构建的逻辑,相当于在直接调用的基础上封装了一层(但是并没什么优越之处),调用如下:
ByteDanceBuilder b1 = new ByteDanceBuilder();
Director d = new Director();
d.build(b1);
App app1 = b1.build();
这个还是将参数封装进去了,要是参数也要传的话……emm,可以想象。
![image.png](https://img.haomeiwen.com/i2441545/3a5a04e805914bfb.png&originHeight=236&originWidth=213&size=38830&status=done&style=none&width=243?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如何选择?
上面提到了2中实现形式,其中第一个是现在比较常用的形式,比如说Okhttp,Retrofit,Notification,AlertDialog等都用到了,直接将Builder封装在实体类或者主类中,而第二种就是默认的形式他的关系图如下。
![image.png](https://img.haomeiwen.com/i2441545/50d195057ada0e4e.png&originHeight=906&originWidth=924&size=543066&status=done&style=none&width=462?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
那么在实际使用中,我们一般是选择第一种,第二种一般是在多个构造者的情况下使用。
至于优缺点简单说下。
优点
- 使用者不必关心产品内部,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品。
- 增加新的具体建造者无须修改原有类库的代码,扩展起来相对容易,可以自行控制产品的创建过程,而且也可以控制调用顺序合法性检查等辅助操作。
缺点
- 建造的产品最好是有很多共同点,要是差异性过大,处理的成分就比较多,不太实用。
- 如果构建器的种类过多,维护起来很困难。
参考
https://juejin.im/post/5a23bdd36fb9a045272568a6
https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html
网友评论