引入
你决定给自己造一栋梦想中的房子。你有很多想法:一个巨大的客厅、带有最新款电器的厨房、一个宽敞的阳台,还有一个带按摩功能的浴缸。问题来了,你会自己动手建这栋房子吗?当然不会,除非你是个全能超人。一般人会怎么做?找一个建筑师和一支施工队,告诉他们你的想法,让专业人士来帮你实现。
在软件开发中,建造者模式就扮演着这样的角色。当你需要创建一个复杂对象时(比如,一个有很多属性和设置的对象),直接把所有东西一股脑塞进构造函数里显然不是个好主意。这就像是把一堆材料扔给建筑师说:“来,造房子!”没有设计图,没有计划,这栋房子肯定是盖不好的。
建造者模式给了我们一个“建筑师”——一个Builder类。这个类一步一步地帮你构建复杂的对象。你可以设置你想要的属性,跳过不关心的部分,最后调用一个“build”方法,得到你想要的对象。这样做的好处是,你的代码会变得更清晰、更易于维护,也更加灵活。
举个例子,假设我们要创建一个Pizza
对象,它有各种属性:大小、酱料类型、是否要辣、是否加奶酪等等。如果用传统的构造函数,你可能需要创建很多版本的构造函数来满足不同的需求。但是,如果使用建造者模式,你就可以轻松地一步步设置你想要的属性:
Pizza myPizza = new Pizza.Builder()
.size("大")
.sauceType("番茄")
.spicy(true)
.cheese(true)
.build();
这样,不仅代码看起来整洁多了,而且以后如果你想要新增或者更改Pizza
的属性,也会变得非常容易。
定义
建造者模式(Builder Pattern)是一种创建型设计模式,其定义如下:
- 建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建出不同的表示。
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
- 建造者模式可以将一个产品的内部表象和生产过程相分离,从而可以通过一个建造过程而创建出不同内部表象的产品对象。
这种模式通常用于创建复杂对象,特别是当对象的构建过程和表示方式需要独立变化时。在这种情况下,使用建造者模式可以使代码更加清晰,更易于控制和维护。
一、建造者模式概述
1.四个基本组成部分
建造者模式的四个基本组成部分包括:
-
Product(产品角色):这是一个具体的产品对象。它包含了多个组成部分,这些部分在建造过程中被构建。
-
Builder(抽象建造者):这是创建一个Product对象的各个部件指定的接口或抽象类。它定义了如何创建各个部件。
-
ConcreteBuilder(具体建造者):这是实现了Builder接口或继承了Builder类的类,它实现了各个部件的具体构造和装配方法,定义并明确了它所创建的复杂对象。
-
Director(指挥者):这是构建一个使用Builder接口的对象的类。它主要负责控制产品对象的生产过程,包括某个部件的建造方法是否被调用,以及调用的先后次序等等。客户端只需要与导演类进行交互,通过导演类的构造函数或者setter将具体建造者对象传入导演类中。
这种模式可以使客户端不需要知道内部的具体构建细节,只需要知道所需的建造者类型即可。这样就可以将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2.与工厂模式的区别
建造者模式(Builder Pattern)和工厂模式(Factory Pattern)都是创建型设计模式,它们在软件开发中用于创建对象,但它们在目的、实现方式和使用场景上有所不同。
① 建造者模式
建造者模式的主要目的是为了构建一个复杂的对象。它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常涉及到一个指导者(Director)和多个建造者(Builder),每个建造者负责构建对象的一部分。
优点:
- 分离了对象子组件的单独构造(由Builder负责)和装配(由Director负责)。这样可以构建复杂的对象。
- 客户端不必知道产品内部组成的细节,将产品本身和产品的构建过程解耦,使得相同的创建过程可以产生不同的产品。
- 增加新的具体建造者无需修改原有类的代码,符合“开闭原则”。
使用场景:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
② 工厂模式
工厂模式主要分为三种:简单工厂模式、工厂方法模式和抽象工厂模式。它们的共同目的是创建对象,但在复杂度和使用场景上有所不同。
简单工厂模式:
- 一个工厂类根据传入的参数决定创建出哪一种产品类的实例。
- 不易于扩展复杂的产品结构。
工厂方法模式:
- 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 易于扩展,符合开闭原则。
抽象工厂模式:
- 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
- 适用于有多个系列产品的情况。
优点:
- 用户只需要知道具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程。
- 在系统增加新的产品时,无需修改现有系统代码,符合开闭原则。
使用场景:
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由其子类来指定创建对象时。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化时。
区别
- 目的和应用场景: 建造者模式适用于创建复杂对象,需要分步骤构造对象的场景;而工厂模式适用于创建产品类的实例,特别是在创建对象的过程不太复杂,但对象的类型很多时。
- 复杂度: 建造者模式通常用于创建更复杂的对象,需要多个部分协同工作;工厂模式则相对简单,主要关注于创建对象。
- 构建过程的控制: 在建造者模式中,构建和表示是分离的,客户端可以控制构建过程;而在工厂模式中,客户端通常只需指定需要的类型,无需关心构建过程。
二、建造者模式的应用场景
1.描述复杂对象的创建过程时
建造者模式通过将一个复杂对象的构建过程分解为多个简单的步骤,并且允许通过相同的构建过程构建出不同的表示(或配置),从而实现复杂对象的创建。它主要包括以下几个角色:
- Builder(建造者):为创建一个产品对象的各个部件指定抽象接口。
- ConcreteBuilder(具体建造者):实现Builder的接口以构造和装配该产品的各个部件。定义并明确它所创建的表示,并提供一个检索产品的接口。
- Director(指导者):构造一个使用Builder接口的对象。
- Product(产品角色):表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
2.当一个对象的构造过程需要多个步骤时
想象一个文档编辑器软件,需要导出包含多个元素(如文本、图片和图表)的复杂文档。这些元素的创建过程可能涉及多个步骤(例如,为图表选择数据源、为图片选择分辨率等)。
在这种情况下,可以使用建造者模式来逐步构建文档。每个元素(文本、图片、图表)可以由不同的建造者(ConcreteBuilder)来构建。指导者(Director)负责管理构建过程的顺序,确保所有元素都按照正确的顺序和配置添加到文档中。
// 建造者接口
interface DocumentBuilder {
void buildText();
void buildImage();
void buildChart();
Document getDocument();
}
// 具体建造者
class RichDocumentBuilder implements DocumentBuilder {
private Document document = new Document();
@Override
public void buildText() {
// 添加复杂文本元素
}
@Override
public void buildImage() {
// 添加高分辨率图片
}
@Override
public void buildChart() {
// 添加详细图表
}
@Override
public Document getDocument() {
return document;
}
}
// 指导者
class Director {
public void constructReport(DocumentBuilder builder) {
builder.buildText();
builder.buildChart();
// 根据需要构建文档的不同部分
}
}
伪代码
使用:
Director director = new Director();
DocumentBuilder builder = new RichDocumentBuilder();
director.constructReport(builder);
Document document = builder.getDocument();
伪代码
3.当需要提供一个对象的不同表示(或配置)时
想象一个汽车制造的过程,汽车可能有不同的配置,如经济型、豪华型或运动型。每种类型的汽车都可能需要不同的组件(如引擎类型、座椅材料、轮胎类型等)。
在这种情况下,建造者模式允许通过指定不同的具体建造者来创建不同类型的汽车。例如,一个EconomyCarBuilder
可能会构建一个配备基础引擎和基本座椅的汽车,而LuxuryCarBuilder
可能会为相同的汽车模型添加高端引擎、豪华座椅和高级音响系统。指导者(Director)根据客户的要求选择合适的建造者来构建客户所需配置的汽车。
通过这种方式,建造者模式使得相同的构建过程可以创建出具有不同表示或配置的对象,同时保持构建过程的一致性和控制。
// 建造者接口
interface CarBuilder {
void buildEngine();
void buildSeats();
void buildTires();
Car getCar();
}
// 经济型汽车建造者
class EconomyCarBuilder implements CarBuilder {
private Car car = new Car();
@Override
public void buildEngine() {
// 构建小型引擎
}
@Override
public void buildSeats() {
// 构建基础座椅
}
@Override
public void buildTires() {
// 构建标准轮胎
}
@Override
public Car getCar() {
return car;
}
}
// 豪华型汽车建造者
class LuxuryCarBuilder implements CarBuilder {
private Car car = new Car();
@Override
public void buildEngine() {
// 构建高性能引擎
}
@Override
public void buildSeats() {
// 构建豪华座椅
}
@Override
public void buildTires() {
// 构建高性能轮胎
}
@Override
public Car getCar() {
return car;
}
}
// 指导者
class CarDirector {
public void constructEconomyCar(CarBuilder builder) {
builder.buildEngine();
builder.buildSeats();
// 根据需要构建汽车的不同部分
}
public void constructLuxuryCar(CarBuilder builder) {
builder.buildEngine();
builder.buildSeats();
builder.buildTires();
// 构建豪华车需要的更多部分
}
}
伪代码
使用:
CarDirector director = new CarDirector();
CarBuilder economyBuilder = new EconomyCarBuilder();
director.constructEconomyCar(economyBuilder);
Car economyCar = economyBuilder.getCar();
CarBuilder luxuryBuilder = new LuxuryCarBuilder();
director.constructLuxuryCar(luxuryBuilder);
Car luxuryCar = luxuryBuilder.getCar();
伪代码
三、建造者模式的实现
我们通过一个更具体的例子来演示建造者模式的实现,这里我们将构建一个简单的餐车订单系统。在这个系统中,我们可以定制不同类型的餐车订单,比如素食餐车、肉食餐车等。
首先,定义Meal
类,它将作为最终的产品。
public class Meal {
private String drink;
private String main;
private String dessert;
// Setters
public void setDrink(String drink) {
this.drink = drink;
}
public void setMain(String main) {
this.main = main;
}
public void setDessert(String dessert) {
this.dessert = dessert;
}
// toString method for displaying meal
@Override
public String toString() {
return "Meal{" +
"drink='" + drink + '\'' +
", main='" + main + '\'' +
", dessert='" + dessert + '\'' +
'}';
}
}
接着,定义MealBuilder
接口,这是建造者模式的核心,它声明了构建产品的各个步骤。
public interface MealBuilder {
void buildDrink();
void buildMain();
void buildDessert();
Meal getMeal();
}
然后,实现具体的建造者。这里我们创建两个建造者:VegMealBuilder
和NonVegMealBuilder
。
public class VegMealBuilder implements MealBuilder {
private Meal meal = new Meal();
@Override
public void buildDrink() {
meal.setDrink("Water");
}
@Override
public void buildMain() {
meal.setMain("Vegetable Burger");
}
@Override
public void buildDessert() {
meal.setDessert("Apple Pie");
}
@Override
public Meal getMeal() {
return meal;
}
}
public class NonVegMealBuilder implements MealBuilder {
private Meal meal = new Meal();
@Override
public void buildDrink() {
meal.setDrink("Coke");
}
@Override
public void buildMain() {
meal.setMain("Chicken Burger");
}
@Override
public void buildDessert() {
meal.setDessert("Ice Cream");
}
@Override
public Meal getMeal() {
return meal;
}
}
最后,我们需要一个Director
类,它将负责按照特定的顺序指导建造过程。
public class MealDirector {
private MealBuilder builder;
public MealDirector(MealBuilder builder) {
this.builder = builder;
}
public void constructMeal() {
builder.buildDrink();
builder.buildMain();
builder.buildDessert();
}
public Meal getMeal() {
return builder.getMeal();
}
}
现在,我们可以使用这个建造者模式来创建不同类型的餐车订单了。
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder vegMealBuilder = new VegMealBuilder();
MealDirector director = new MealDirector(vegMealBuilder);
director.constructMeal();
Meal vegMeal = director.getMeal();
System.out.println("Veg Meal: " + vegMeal);
MealBuilder nonVegMealBuilder = new NonVegMealBuilder();
director = new MealDirector(nonVegMealBuilder);
director.constructMeal();
Meal nonVegMeal = director.getMeal();
System.out.println("Non-Veg Meal: " + nonVegMeal);
}
}
这样的设计使得增加新类型的餐车订单变得更加简单,只需添加新的具体建造者类即可。
四、建造者模式的优点与缺点
优点
- 分离了产品的构造和表示
- 提高了构造复杂对象的灵活性和清晰度
- 可以改变一个已经构建的对象的内部表示
缺点
- 导致设计中增加许多具体的Builder类
- 需要更多的编写工作,因为要分别创建Builder和Director
五、建造者模式的现代应用
1.在现代编程语言中的应用
Java中的StringBuilder
StringBuilder
是Java中一个典型的建造者模式应用。在Java中,字符串是不可变的,每次对字符串的修改实际上都会生成一个新的字符串对象,这在进行大量字符串操作时会导致性能问题。StringBuilder
类提供了一个可变的字符串,允许在原有字符串的基础上进行修改,从而避免了频繁创建字符串对象的开销。StringBuilder
通过提供诸如append
、insert
等方法,让用户可以按照需要构建字符串,最后通过toString
方法获取最终的字符串结果。
Java 8中的Stream API
Java 8引入的Stream API也可以看作是建造者模式的一种应用。通过一系列的操作(如filter
、map
、sorted
等),用户可以构建出复杂的数据处理管道。每一次操作都会返回一个新的Stream对象,直到最终通过一个终端操作(如collect
、forEach
)完成整个数据处理过程。
2.在软件工程中的应用案例
Web请求构建
在构建Web请求(如HTTP请求)时,请求的构建过程可能包含设置URL、设置请求方法(GET、POST等)、添加头部、设置请求体等多个步骤。使用建造者模式可以帮助我们灵活地构建出不同类型的请求。例如,可以为HTTP请求定义一个HttpRequestBuilder
,通过链式调用方法设置请求的各个部分,最后构建出完整的请求对象。
GUI窗口设计
在GUI应用程序中,窗口和对话框的创建通常涉及多个组件的配置和布局。使用建造者模式可以简化这一过程,允许开发者通过一系列的方法调用来配置窗口的属性,如大小、标题、布局以及包含的组件等,最后构建出窗口对象。
3.如何在你的项目中实现建造者模式
要在你的项目中实现建造者模式,可以遵循以下步骤:
-
定义产品类:首先定义要构建的复杂对象类,它可能包含多个属性。
-
创建建造者接口:定义一个建造者接口,声明构建复杂对象所需的所有步骤。
-
实现具体建造者:为每一种表示创建具体的建造者类,实现建造者接口中声明的所有步骤,并保持构建过程的结果。
-
定义指导者类:创建一个指导者类,它负责按照特定的顺序来调用建造者接口中的方法,以构建出复杂对象。
-
客户端使用:客户端创建具体建造者和指导者对象,通过指导者来构建出复杂对象。
总结
在我们的探索中,我们深入了解了建造者模式——一种设计模式,它通过将复杂对象的构建与其表示分离来增加构造复杂对象的灵活性和代码的清晰度。通过详细讨论建造者模式的定义、核心组成、实现步骤、优缺点以及现代应用,我们希望为读者提供了一个全面的视角,以更好地理解和应用这一模式。
建造者模式由四个主要角色组成:产品(Product)、建造者(Builder)、具体建造者(ConcreteBuilder)和指导者(Director)。这种分工合作的模式不仅确保了构建过程的灵活性,还保持了代码的整洁和可维护性。
对于建造者模式的多种应用场景,包括复杂对象的创建和多种表示的对象。这种模式特别适用于那些需要通过多个步骤构建对象的情况,以及那些对象可能有多种表示形式的情况。
尽管建造者模式提供了许多优点,如分离了产品的构造和表示、提高了构造复杂对象的灵活性,但它也有其缺点,包括可能导致设计中增加许多具体的Builder类,以及需要更多的编写工作。
对于建造者模式在现代编程语言和软件工程中的应用,从Java的StringBuilder到Web请求构建和GUI窗口设计,这些例子都展示了建造者模式如何在实际开发中提供清晰、灵活的解决方案。
建造者模式是一种强大的设计工具,适用于许多编程和软件开发场景。通过在你的项目中实施建造者模式,你可以提高代码的可读性、可维护性和灵活性,从而创建出更加健壮和可扩展的应用程序。