【再谈设计模式】建造者模式~对象构建的指挥家
一、引言
在软件开发的世界里,创建对象是一项基本且频繁的操作。然而,当对象的构造变得复杂,涉及众多属性和初始化步骤时,传统的构造函数方式往往会让代码陷入混乱的泥沼。就如同搭建一座复杂的建筑,若没有合理的规划和组织,各个部件随意堆砌,最终的结果可能是摇摇欲坠。在这种情况下,建造者设计模式就如同一位经验丰富的建筑师,为我们提供了一种有条不紊地构建复杂对象的方法,它将构建过程与对象的最终表示分离开来,使我们能够轻松应对复杂对象创建的挑战。
二、定义与描述
建造者设计模式属于创建型设计模式,它将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。这种模式允许你使用相同的构建代码创建不同类型和配置的对象。
三、抽象背景
在软件开发中,经常会遇到创建复杂对象的情况。如果直接使用构造函数或者简单的工厂方法来创建对象,可能会导致构造函数参数过多,代码难以维护和理解。例如,创建一个包含多个属性(如房屋的面积、房间数量、装修风格等)的对象时,直接构造可能会变得非常复杂。
四、适用场景与现实问题
解决复杂对象创
当创建的对象具有多个可选参数或者复杂的初始化过程时,建造者模式可以清晰地分离构建步骤,使代码更易读和维护。
例如,在创建一个汽车对象时,汽车可能有不同的颜色、型号、配置(如自动或手动档)等属性。使用建造者模式可以方便地根据用户需求组合这些属性来创建汽车对象。
对象创建过程的定制化
如果需要根据不同的条件或需求定制对象的创建过程,建造者模式很有用。
比如在构建一份个性化的餐食订单,根据顾客的不同口味选择(主食、配菜、饮料等)构建不同的餐食对象。
五、现实生活的例子
以建造房屋为例。
建造一座房子是一个复杂的过程,包括打地基、砌墙、盖屋顶等步骤。
有不同类型的房子,如别墅、公寓等,它们的建造过程基本相同,但最终呈现的样子(表示)不同。
建筑工人(建造者)按照建筑师(导演者)的要求,通过相同的基本建造步骤(打地基、砌墙、盖屋顶等),但使用不同的材料或者设计风格,建造出不同类型的房子。
六、初衷与问题解决
初衷
为了应对复杂对象的创建,将对象的构建过程从对象本身的表示中分离出来,提高代码的可维护性和可扩展性。
问题解决
解决了构造函数参数过多的问题。例如,原本创建一个复杂对象可能需要一个包含十几个参数的构造函数,使用建造者模式后,可以通过多个简单的设置方法来逐步构建对象。
便于对象的定制化创建,满足不同用户或场景的需求。
七、代码示例
Java示例
// 产品类:房屋
class House {
private String foundation;
private String walls;
private String roof;
public House(String foundation, String walls, String roof) {
this.foundation = foundation;
this.walls = walls;
this.roof = roof;
}
public String toString() {
return "House with foundation: " + foundation + ", walls: " + walls + ", roof: " + roof;
}
}
// 抽象建造者
abstract class HouseBuilder {
protected House house;
public House getHouse() {
return house;
}
public abstract void buildFoundation();
public abstract void buildWalls();
public abstract void buildRoof();
}
// 具体建造者:别墅建造者
class VillaBuilder extends HouseBuilder {
public VillaBuilder() {
house = new House("", "", "");
}
@Override
public void buildFoundation() {
house.foundation = "Deep and large foundation for villa";
}
@Override
public void buildWalls() {
house.walls = "High - quality stone walls for villa";
}
@Override
public void buildRoof() {
house.roof = "Sloping roof for villa";
}
}
// 导演者
class HouseDirector {
public House constructHouse(HouseBuilder builder) {
builder.buildFoundation();
builder.buildWalls();
builder.buildRoof();
return builder.getHouse();
}
}
// 测试
public class BuilderPatternJava {
public static void main(String[] args) {
HouseDirector director = new HouseDirector();
HouseBuilder villaBuilder = new VillaBuilder();
House villa = director.constructHouse(villaBuilder);
System.out.println(villa);
}
}
C++示例
#include <iostream>
#include <string>
// 产品类:汽车
class Car {
private:
std::string color;
std::string model;
std::string gearType;
public:
Car(std::string color, std::string model, std::string gearType) : color(color), model(model), gearType(gearType) {}
friend std::ostream& operator<<(std::ostream& os, const Car& car) {
os << "Car - Color: " + car.color + ", Model: " + car.model + ", Gear Type: " + car.gearType;
return os;
}
}
// 抽象建造者
class CarBuilder {
protected:
Car* car;
public:
Car* getCar() {
return car;
}
virtual void buildColor() = 0;
virtual void buildModel() = 0;
virtual void buildGearType() = 0;
}
// 具体建造者:跑车建造者
class SportsCarBuilder : public CarBuilder {
public:
SportsCarBuilder() {
car = new Car("", "", "");
}
void buildColor() override {
car->color = "Red";
}
void buildModel() override {
car->model = "Sports Model";
}
void buildGearType() override {
car->gearType = "Manual";
}
}
// 导演者
class CarDirector {
public:
Car* constructCar(CarBuilder* builder) {
builder->buildColor();
builder->buildModel();
builder->buildGearType();
return builder->getCar();
}
}
// 测试
int main() {
CarDirector director;
CarBuilder* sportsCarBuilder = new SportsCarBuilder();
Car* sportsCar = director.constructCar(sportsCarBuilder);
std::cout << *sportsCar << std::endl;
delete sportsCarBuilder;
delete sportsCar;
return 0;
}
Python示例
# 产品类:披萨
class Pizza:
def __init__(self):
self.dough = None
self.sauce = None
self.toppings = []
def __str__(self):
topping_str = ', '.join(self.toppings)
return f"Pizza with {self.dough} dough, {self.sauce} sauce and {topping_str} toppings"
# 抽象建造者
class PizzaBuilder:
def __init__(self):
self.pizza = Pizza()
def get_pizza(self):
return self.pizza
def build_dough(self):
pass
def build_sauce(self):
pass
def add_toppings(self):
pass
# 具体建造者:夏威夷披萨建造者
class HawaiianPizzaBuilder(PizzaBuilder):
def build_dough(self):
self.pizza.dough = "Thin"
def build_sauce(self):
self.pizza.sauce = "Tomato"
def add_toppings(self):
self.pizza.toppings = ["Ham", "Pineapple"]
# 导演者
class PizzaDirector:
def construct_pizza(self, builder):
builder.build_dough();
builder.build_sauce();
builder.add_toppings();
return builder.get_pizza()
# 测试
if __name__ == "__main__":
director = PizzaDirector()
builder = HawaiianPizzaBuilder()
pizza = director.construct_pizza(builder)
print(pizza)
Go示例
// 产品类:电脑
type Computer struct {
cpu string
memory string
storage string
}
func (c Computer) String() string {
return "Computer with CPU: " + c.cpu + ", Memory: " + c.memory + ", Storage: " + c.storage
}
// 抽象建造者
type ComputerBuilder interface {
BuildCPU()
BuildMemory()
BuildStorage()
GetComputer() Computer
}
// 具体建造者:游戏电脑建造者
type GamingComputerBuilder struct {
computer Computer
}
func (g *GamingComputerBuilder) BuildCPU() {
g.computer.cpu = "High - performance CPU"
}
func (g *GamingComputerBuilder) BuildMemory() {
g.computer.memory = "Large - capacity memory"
}
func (g *GamingComputerBuilder) BuildStorage() {
g.computer.storage = "High - speed storage"
}
func (g *GamingComputerBuilder) GetComputer() Computer {
return g.computer
}
// 导演者
type ComputerDirector struct{}
func (d ComputerDirector) ConstructComputer(builder ComputerBuilder) Computer {
builder.BuildCPU()
builder.BuildMemory()
builder.BuildStorage()
return builder.GetComputer()
}
// 测试
func main() {
director := ComputerDirector{}
builder := &GamingComputerBuilder{}
computer := director.ConstructComputer(builder)
println(computer)
}
八、建造者设计模式的优缺点
优点
解耦对象的构建和表示
使得构建过程和最终对象的表示可以独立变化,便于代码的维护和扩展。
易于创建复杂对象
可以分步骤构建对象,代码结构清晰,易于理解和编写。
代码复用性高
可以复用相同的构建步骤来创建不同类型的对象。
缺点
增加代码复杂度
需要定义多个类(建造者、导演者、产品类等),对于简单的对象创建可能会使代码过于复杂。
可能存在多余的建造者类
如果对象的构建方式比较固定,可能不需要那么多不同的建造者类,会造成代码冗余。
九、建造者设计模式的升级版
链式建造者模式(Fluent Builder Pattern)
在传统建造者模式的基础上,将建造者方法的返回值设置为建造者本身,这样可以实现链式调用。
例如在Java中,可以将建造者方法返回 this。
优点是使代码更加紧凑和易于阅读,特别是在构建对象时可以在一行中完成多个属性的设置。
缺点是如果构建过程中出现错误,调试可能会比较复杂,因为链式调用可能会隐藏一些中间状态。
建造者设计模式在处理复杂对象创建方面有着独特的优势,通过合理的运用可以提高代码的质量和可维护性,并且在需要定制化创建对象的场景下非常实用。