本文是我们名为“ Java设计模式 ”的学院课程的一部分。
在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看 !
建造者模式
通常,对象构造的细节(例如,实例化和初始化组成对象的组件)通常作为对象构造函数的一部分保留在对象内。 这种设计将对象的构建过程与组成对象的组件紧密地联系在一起。 只要正在构造的对象简单且对象的构造过程是确定的,并且始终产生相同的对象表示,则此方法适用。
但是,当要创建的对象很复杂并且构成对象创建过程的一系列步骤可以以不同的方式执行,从而产生对象的不同表示形式时,此设计可能无效。 因为构造过程的不同实现都保存在对象中,所以对象可能变得笨重(构造膨胀)并且模块化程度降低。 随后,添加新的实现或对现有实现进行更改需要对现有代码进行更改。
使用Builder模式,可以更有效地设计构造此类对象的过程。 Builder模式建议将构造逻辑从对象类中移到一个单独的类中,称为Builder类。 可以有多个这样的构建器类,每种构建器类对于构建对象的一系列步骤都具有不同的实现。 每个构建器实现都会导致对象的不同表示形式。
为了说明构建器模式的使用,让我们帮助一家汽车公司使用图形模型向其客户显示其不同的汽车。 该公司有一个图形工具,可以在屏幕上显示汽车。 该工具的要求是为其提供汽车对象。 汽车对象应包含汽车的规格。 图形工具使用这些规格来显示汽车。 该公司已将其汽车分为轿车或跑车等不同类别。 只有一个汽车对象,我们的工作是根据分类创建汽车对象。 例如,对于轿车,应构建符合轿车规格的汽车对象,或者,如果需要跑车,则应构建符合跑车规格的汽车对象。 目前,公司仅需要这两种类型的汽车,但将来可能还会需要其他类型的汽车。
我们将创建两个不同的构建器,每种分类中的一个,即轿车和跑车。 这两个建造者将帮助我们根据其规格建造汽车物体。 但是在此之前,让我们讨论“构建器模式”的一些细节。
2.什么是构建器模式
构建器模式的目的是将复杂对象的构造与其表示分离,以便同一构造过程可以创建不同的表示。 这种类型的分离减小了物体的尺寸。 事实证明,该设计具有更高的模块化,每个实现都包含在不同的构建器对象中。 添加新的实现(即添加新的构建器)变得更加容易。 对象构造过程变得独立于组成对象的组件。 这提供了对对象构造过程的更多控制。
在实现方面,可以将构建过程中的每个不同步骤声明为要由不同的混凝土构建者实现的公共接口的方法。
客户端对象可以创建具体构建器的实例,并调用构造最终对象的不同部分所需的方法集。 这种方法要求每个客户对象都了解构造逻辑。 每当构造逻辑发生变化时,所有客户端对象都需要进行相应的修改。
构建器模式引入了另一种隔离级别,可以解决此问题。 构建器模式建议不要使用客户端对象直接调用不同的构建器方法,而建议使用称为Director的专用对象,该对象负责调用构建最终对象所需的不同构建器方法。 不同的客户端对象可以使用Director对象创建所需的对象。 构造对象后,客户端对象可以直接向构建器请求完全构造的对象。 为了简化此过程,可以在公共Builder界面中声明一个新方法getObject
,以由不同的具体构建器实现。
新的设计消除了使用客户端对象处理构成对象构造过程的方法的需要,并封装了如何从客户端构造对象的细节。
建造者
- 指定用于创建产品对象各部分的抽象接口。
混凝土建筑工
- 通过实现Builder接口来构造和组装产品的各个部分。
- 定义并跟踪其创建的表示形式。
- 提供用于检索产品的界面。
导向器
- 使用Builder接口构造一个对象。
产品
- 表示正在构造的复杂对象。 ConcreteBuilder构建产品的内部表示形式,并定义其组装过程。
- 包括定义组成零件的类,包括用于将零件组装成最终结果的接口。
3.实施构建器模式
打击是“ Car
类(产品),其中包含一些用于构造完整car
对象所需的汽车重要组件。
package com.javacodegeeks.patterns.builderpattern;
public class Car {
private String bodyStyle;
private String power;
private String engine;
private String breaks;
private String seats;
private String windows;
private String fuelType;
private String carType;
public Car (String carType){
this.carType = carType;
}
public String getBodyStyle() {
return bodyStyle;
}
public void setBodyStyle(String bodyStyle) {
this.bodyStyle = bodyStyle;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getBreaks() {
return breaks;
}
public void setBreaks(String breaks) {
this.breaks = breaks;
}
public String getSeats() {
return seats;
}
public void setSeats(String seats) {
this.seats = seats;
}
public String getWindows() {
return windows;
}
public void setWindows(String windows) {
this.windows = windows;
}
public String getFuelType() {
return fuelType;
}
public void setFuelType(String fuelType) {
this.fuelType = fuelType;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("--------------"+carType+"--------------------- \\n");
sb.append(" Body: ");
sb.append(bodyStyle);
sb.append("\\n Power: ");
sb.append(power);
sb.append("\\n Engine: ");
sb.append(engine);
sb.append("\\n Breaks: ");
sb.append(breaks);
sb.append("\\n Seats: ");
sb.append(seats);
sb.append("\\n Windows: ");
sb.append(windows);
sb.append("\\n Fuel Type: ");
sb.append(fuelType);
return sb.toString();
}
}
CarBuilder
是构建器接口,包含用于构建car
对象及其组件的一组常用方法。
package com.javacodegeeks.patterns.builderpattern;
public interface CarBuilder {
public void buildBodyStyle();
public void buildPower();
public void buildEngine();
public void buildBreaks();
public void buildSeats();
public void buildWindows();
public void buildFuelType();
public Car getCar();
}
getCar
方法用于在构造完成后将最终的汽车对象返回给客户端。
让我们看一下CarBuilder
接口的两种实现,一种适用于每种汽车,即轿车和跑车。
package com.javacodegeeks.patterns.builderpattern;
public class SedanCarBuilder implements CarBuilder{
private final Car car = new Car("SEDAN");
@Override
public void buildBodyStyle() {
car.setBodyStyle("External dimensions: overall length (inches): 202.9, " +
"overall width (inches): 76.2, overall height (inches): 60.7, wheelbase (inches): 112.9," +
" front track (inches): 65.3, rear track (inches): 65.5 and curb to curb turning circle (feet): 39.5");
}
@Override
public void buildPower(){
car.setPower("285 hp @ 6,500 rpm; 253 ft lb of torque @ 4,000 rpm");
}
@Override
public void buildEngine() {
car.setEngine("3.5L Duramax V 6 DOHC");
}
@Override
public void buildBreaks() {
car.setBreaks("Four-wheel disc brakes: two ventilated. Electronic brake distribution");
}
@Override
public void buildSeats() {
car.setSeats("Front seat center armrest.Rear seat center armrest.Split-folding rear seats");
}
@Override
public void buildWindows() {
car.setWindows("Laminated side windows.Fixed rear window with defroster");
}
@Override
public void buildFuelType() {
car.setFuelType("Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range");
}
@Override
public Car getCar(){
return car;
}
}
package com.javacodegeeks.patterns.builderpattern;
public class SportsCarBuilder implements CarBuilder{
private final Car car = new Car("SPORTS");
@Override
public void buildBodyStyle() {
car.setBodyStyle("External dimensions: overall length (inches): 192.3," +
" overall width (inches): 75.5, overall height (inches): 54.2, wheelbase (inches): 112.3," +
" front track (inches): 63.7, rear track (inches): 64.1 and curb to curb turning circle (feet): 37.7");
}
@Override
public void buildPower(){
car.setPower("323 hp @ 6,800 rpm; 278 ft lb of torque @ 4,800 rpm");
}
@Override
public void buildEngine() {
car.setEngine("3.6L V 6 DOHC and variable valve timing");
}
@Override
public void buildBreaks() {
car.setBreaks("Four-wheel disc brakes: two ventilated. Electronic brake distribution. StabiliTrak stability control");
}
@Override
public void buildSeats() {
car.setSeats("Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments");
}
@Override
public void buildWindows() {
car.setWindows("Front windows with one-touch on two windows");
}
@Override
public void buildFuelType() {
car.setFuelType("Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range");
}
@Override
public Car getCar(){
return car;
}
}
上面的两个建造者根据所需规范创建和构造产品,即car
对象。
现在,让我们测试Builder。
package com.javacodegeeks.patterns.builderpattern;
public class TestBuilderPattern {
public static void main(String[] args) {
CarBuilder carBuilder = new SedanCarBuilder();
CarDirector director = new CarDirector(carBuilder);
director.build();
Car car = carBuilder.getCar();
System.out.println(car);
carBuilder = new SportsCarBuilder();
director = new CarDirector(carBuilder);
director.build();
car = carBuilder.getCar();
System.out.println(car);
}
}
上面的代码将导致以下输出。
--------------SEDAN---------------------
Body: External dimensions: overall length (inches): 202.9, overall width (inches): 76.2, overall height (inches): 60.7, wheelbase (inches): 112.9, front track (inches): 65.3, rear track (inches): 65.5 and curb to curb turning circle (feet): 39.5
Power: 285 hp @ 6,500 rpm; 253 ft lb of torque @ 4,000 rpm
Engine: 3.5L Duramax V 6 DOHC
Breaks: Four-wheel disc brakes: two ventilated. Electronic brake distribution
Seats: Front seat center armrest.Rear seat center armrest.Split-folding rear seats
Windows: Laminated side windows.Fixed rear window with defroster
Fuel Type: Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range
--------------SPORTS---------------------
Body: External dimensions: overall length (inches): 192.3, overall width (inches): 75.5, overall height (inches): 54.2, wheelbase (inches): 112.3, front track (inches): 63.7, rear track (inches): 64.1 and curb to curb turning circle (feet): 37.7
Power: 323 hp @ 6,800 rpm; 278 ft lb of torque @ 4,800 rpm
Engine: 3.6L V 6 DOHC and variable valve timing
Breaks: Four-wheel disc brakes: two ventilated. Electronic brake distribution. StabiliTrak stability control
Seats: Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments
Windows: Front windows with one-touch on two windows
Fuel Type: Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range
在上面的类,我们首先创建一个SedanCarBuilder
和CarDirector
。 然后,我们要求CarDirector
根据传递给它的builder
为我们制造car
。 最后,我们直接要求builder
向我们提供创建的car
对象。
我们对SportsCarBuilder
进行了同样的操作,该car
根据跑车规范返回car
对象。
使用“构建器模式”的方法足够灵活,可以在将来更改任何新型汽车而无需更改任何现有代码。 我们所需要做的就是根据新车的规格创建一个新的建造者,并将其提供给总监进行建造。
4. Builder模式的另一种形式
到目前为止,我们还没有看到其他形式的构建器模式。 有时,一个对象的属性列表很长,并且其中大多数属性都是可选的。 考虑一个在线表格,需要填写该表格才能成为站点的成员。 您需要填写所有必填字段,但是可以跳过可选字段,有时填充某些可选字段可能看起来很有价值。
请检查下面的Form
类,其中包含一长串属性,并且某些属性是可选的。 表单中必须具有firstName
, lastName
, userName
和password
,但其他所有字段均为可选字段。
package com.javacodegeeks.patterns.builderpattern;
import java.util.Date;
public class Form {
private String firstName;
private String lastName;
private String userName;
private String password;
private String address;
private Date dob;
private String email;
private String backupEmail;
private String spouseName;
private String city;
private String state;
private String country;
private String language;
private String passwordHint;
private String secuirtyQuestion;
private String securityAnswer;
}
问题是,我们应该为此类编写什么样的构造函数? 编写具有长参数列表的构造函数不是一个好选择,这可能会使客户端感到沮丧,尤其是在重要字段很少的情况下。 这可能会增加错误的范围; 客户可能会误将值提供给错误的字段。
另一种方法是使用伸缩构造函数,在这种构造函数中,您只为构造函数提供必需的参数,为另一个构造函数提供单个可选参数,为第三个构造函数提供两个可选参数,依此类推,最后构造出具有所有可选参数的构造函数。
伸缩构造函数可能看起来像这样。
public Form(String firstName,String lastName){
this(firstName,lastName,null,null);
}
public Form(String firstName,String lastName,String userName,String password){
this(firstName,lastName,userName,password,null,null);
}
public Form(String firstName,String lastName,String userName,String password,String address,Date dob){
this(firstName,lastName,userName,password,address,dob,null,null);
}
public Form(String firstName,String lastName,String userName,String password,String address,Date dob,String email,String backupEmail){
…
}
当您要创建实例时,请使用带有最短参数列表的构造函数,该列表包含要设置的所有参数。
伸缩式构造函数可以工作,但是当有许多参数时,很难编写客户端代码,甚至更难读取。 读者不知道所有这些值意味着什么,必须仔细计数参数才能找出答案。 相同类型的参数的长序列可能会导致细微的错误。 如果客户端意外地反转了两个这样的参数,则编译器不会抱怨,但是程序在运行时会出现异常。
当面对许多构造函数参数时,第二种替代方法是JavaBeans模式,在该模式中,您调用一个参数较少的构造函数来创建对象,然后调用setter方法来设置每个所需的参数和每个感兴趣的可选参数。
不幸的是,JavaBeans模式本身具有严重的缺点。 因为构造是在多个调用之间进行的,所以JavaBean在构造过程中可能处于不一致的状态。 该类不能仅通过检查构造函数参数的有效性来强制执行一致性。 在对象处于不一致状态时尝试使用该对象可能会导致失败,而这些失败与包含该错误的代码相距甚远,因此难以调试。
第三种选择是将伸缩构造函数模式的安全性与JavaBeans模式的可读性结合在一起。 它是Builder模式的一种形式。 客户端不是直接制作所需的对象,而是使用所有必需的参数调用构造函数并获取一个生成器对象。 然后,客户端在构建器对象上调用类似于setter的方法来设置每个感兴趣的可选参数。 最后,客户端调用无参数构建方法来生成对象。
package com.javacodegeeks.patterns.builderpattern;
import java.util.Date;
public class Form {
private String firstName;
private String lastName;
private String userName;
private String password;
private String address;
private Date dob;
private String email;
private String backupEmail;
private String spouseName;
private String city;
private String state;
private String country;
private String language;
private String passwordHint;
private String securityQuestion;
private String securityAnswer;
public static class FormBuilder {
private String firstName;
private String lastName;
private String userName;
private String password;
private String address;
private Date dob;
private String email;
private String backupEmail;
private String spouseName;
private String city;
private String state;
private String country;
private String language;
private String passwordHint;
private String securityQuestion;
private String securityAnswer;
public FormBuilder(String firstName, String lastName, String userName, String password){
this.firstName = firstName;
this.lastName = lastName;
this.userName = userName;
this.password = password;
}
public FormBuilder address(String address){
this.address = address;
return this;
}
public FormBuilder dob(Date dob){
this.dob = dob;
return this;
}
public FormBuilder email(String email){
this.email = email;
return this;
}
public FormBuilder backupEmail(String backupEmail){
this.backupEmail = backupEmail;
return this;
}
public FormBuilder spouseName(String spouseName){
this.spouseName = spouseName;
return this;
}
public FormBuilder city(String city){
this.city = city;
return this;
}
public FormBuilder state(String state){
this.state = state;
return this;
}
public FormBuilder country(String country){
this.country = country;
return this;
}
public FormBuilder language(String language){
this.language = language;
return this;
}
public FormBuilder passwordHint(String passwordHint){
this.passwordHint = passwordHint;
return this;
}
public FormBuilder securityQuestion(String securityQuestion){
this.securityQuestion = securityQuestion;
return this;
}
public FormBuilder securityAnswer(String securityAnswer){
this.securityAnswer = securityAnswer;
return this;
}
public Form build(){
return new Form(this);
}
}
private Form(FormBuilder formBuilder){
this.firstName = formBuilder.firstName;
this.lastName = formBuilder.lastName;
this.userName = formBuilder.userName;
this.password = formBuilder.password;
this.address = formBuilder.address;
this.dob = formBuilder.dob;
this.email = formBuilder.email;
this.backupEmail = formBuilder.backupEmail;
this.spouseName = formBuilder.spouseName;
this.city = formBuilder.city;
this.state = formBuilder.state;
this.country = formBuilder.country;
this.language = formBuilder.language;
this.passwordHint = formBuilder.passwordHint;
this.securityQuestion = formBuilder.securityQuestion;
this.securityAnswer = formBuilder.securityAnswer;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(" First Name: ");
sb.append(firstName);
sb.append("\\n Last Name: ");
sb.append(lastName);
sb.append("\\n User Name: ");
sb.append(userName);
sb.append("\\n Password: ");
sb.append(password);
if(this.address!=null){
sb.append("\\n Address: ");
sb.append(address);
}
if(this.dob!=null){
sb.append("\\n DOB: ");
sb.append(dob);
}
if(this.email!=null){
sb.append("\\n Email: ");
sb.append(email);
}
if(this.backupEmail!=null){
sb.append("\\n Backup Email: ");
sb.append(backupEmail);
}
if(this.spouseName!=null){
sb.append("\\n Spouse Name: ");
sb.append(spouseName);
}
if(this.city!=null){
sb.append("\\n City: ");
sb.append(city);
}
if(this.state!=null){
sb.append("\\n State: ");
sb.append(state);
}
if(this.country!=null){
sb.append("\\n Country: ");
sb.append(country);
}
if(this.language!=null){
sb.append("\\n Language: ");
sb.append(language);
}
if(this.passwordHint!=null){
sb.append("\\n Password Hint: ");
sb.append(passwordHint);
}
if(this.securityQuestion!=null){
sb.append("\\n Security Question: ");
sb.append(securityQuestion);
}
if(this.securityAnswer!=null){
sb.append("\\n Security Answer: ");
sb.append(securityAnswer);
}
return sb.toString();
}
public static void main(String[] args) {
Form form = new Form.FormBuilder("Dave", "Carter", "DavCarter", "DAvCaEr123").passwordHint("MyName").city("NY").language("English").build();
System.out.println(form);
}
}
上面的代码将产生以下输出:
First Name: Dave
Last Name: Carter
User Name: DavCarter
Password: DAvCaEr123
City: NY
Language: English
Password Hint: MyName
您可以清楚地看到,现在客户只需要提供必填字段和对他来说很重要的字段。 现在要创建表单对象,我们需要调用FormBuilder
构造函数,该构造函数接受必填字段,然后我们需要在其上调用一组必需的方法,最后调用build
方法来获取表单对象。
5.何时使用构建器模式
在以下情况下使用构建器模式
- 创建复杂对象的算法应独立于组成对象的零件及其组装方式。
- 构造过程必须允许所构造的对象具有不同的表示形式。
6. JDK中的构建器模式
-
java.lang.StringBuilder#append()
(未同步) -
java.lang.StringBuffer#append()
(已同步) -
java.nio.ByteBuffer#put()
(同样在CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer和DoubleBuffer上) -
javax.swing.GroupLayout.Group#addComponent()
-
java.lang.Appendable
所有实现
7.下载源代码
这是关于“构建器模式”的一课。 您可以在此处下载源代码: Builder Pattern Project
翻译自: https://www.javacodegeeks.com/2015/09/builder-design-pattern.html