Published on

建造者模式详解

Authors
  • avatar
    Name
    青雲
    Twitter

在软件设计中,建造者模式(Builder Pattern)是一种创建复杂对象的设计模式。它的主要思想是将对象的构造过程分离出来,使得同样的构建过程可以创建不同的表示。建造者模式特别适用于创建那些包含多个配置项的对象,这些配置项可能需要多步骤或多阶段完成。通过建造者模式,我们可以按照步骤灵活地构建对象,而不是一次性通过构造函数初始化对象。

为什么需要建造者模式?

设想你正在设计一个复杂的对象,比如一辆汽车。这辆汽车包含多个独立配置项,如发动机、车轮、座椅、颜色等。如果直接通过构造函数或者静态方法来初始化这个复杂的对象,会显得过于冗长和难以维护。

建造者模式能够将对象的创建过程分阶段进行,每个阶段能设置对象的某个部分,最后通过一个统一的方法完成对象的创建。这样不仅使代码更清晰,也更容易拓展。

基本结构

建造者模式通常由以下几个部分组成:

  1. 产品(Product):需要构建的复杂对象。
  2. 建造者接口(Builder):定义创建产品各部分的方法。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,具体构建产品的各部分。
  4. 指导者(Director):负责按照顺序调用 Builder 构建产品,指导构造过程。

示例

定义产品类

首先,我们定义一个代表产品的类。

class Car {
  engine: string;
  wheels: number;
  seats: number;
  color: string;

  display(): void {
    console.log(`
      Engine: ${this.engine}
      Wheels: ${this.wheels}
      Seats: ${this.seats}
      Color: ${this.color}
    `);
  }
}

定义建造者接口

接下来,我们定义一个 CarBuilder 接口,声明构建产品各部分的方法。

interface CarBuilder {
  reset(): void;
  setEngine(engine: string): void;
  setWheels(wheels: number): void;
  setSeats(seats: number): void;
  setColor(color: string): void;
  getResult(): Car;
}

实现具体建造者

具体的建造者类实现 CarBuilder 接口,具体构建产品的各部分。

class ConcreteCarBuilder implements CarBuilder {
  private car: Car;

  constructor() {
    this.car = new Car();
  }

  reset(): void {
    this.car = new Car();
  }

  setEngine(engine: string): void {
    this.car.engine = engine;
  }

  setWheels(wheels: number): void {
    this.car.wheels = wheels;
  }

  setSeats(seats: number): void {
    this.car.seats = seats;
  }

  setColor(color: string): void {
    this.car.color = color;
  }

  getResult(): Car {
    return this.car;
  }
}

定义指导者

指导者类负责指导建造过程,按照顺序调用 CarBuilder 的方法。

class CarDirector {
  private builder: CarBuilder;

  constructor(builder: CarBuilder) {
    this.builder = builder;
  }

  constructSportsCar(): void {
    this.builder.reset();
    this.builder.setEngine("V8");
    this.builder.setWheels(4);
    this.builder.setSeats(2);
    this.builder.setColor("Red");
  }

  constructSUV(): void {
    this.builder.reset();
    this.builder.setEngine("V6");
    this.builder.setWheels(4);
    this.builder.setSeats(5);
    this.builder.setColor("Black");
  }
}

使用建造者模式创建产品

最后,我们创建一个 ConcreteCarBuilder 实例,并通过 CarDirector 构建不同类型的汽车。

const builder = new ConcreteCarBuilder();
const director = new CarDirector(builder);

// 构建运动型汽车
director.constructSportsCar();
const sportsCar = builder.getResult();
sportsCar.display(); // 输出:Engine: V8, Wheels: 4, Seats: 2, Color: Red

// 构建SUV
director.constructSUV();
const suv = builder.getResult();
suv.display(); // 输出:Engine: V6, Wheels: 4, Seats: 5, Color: Black

应用场景

组件配置

在现代前端框架(如React、Vue)中,组件通常具有各种配置选项和属性。通过建造者模式,可以更加灵活和清晰地进行组件配置。

class ButtonBuilder {
  constructor() {
    this.button = {
      type: 'button',
      label: 'Default Button',
      onClick: () => {}
    };
  }

  setType(type) {
    this.button.type = type;
    return this;
  }

  setLabel(label) {
    this.button.label = label;
    return this;
  }

  setOnClick(onClick) {
    this.button.onClick = onClick;
    return this;
  }

  build() {
    return this.button;
  }
}

// 使用建造者模式创建按钮组件
const button = new ButtonBuilder()
  .setType('submit')
  .setLabel('Submit')
  .setOnClick(() => alert('Button clicked'))
  .build();

// 按钮组件
function Button({ type, label, onClick }) {
  return <button type={type} onClick={onClick}>{label}</button>;
}

// 测试组件
ReactDOM.render(<Button {...button} />, document.getElementById('root'));

表单构建

复杂表单往往包含多个字段,通过建造者模式,可以更加灵活地构建表单,设置表单字段及其属性。

class FormBuilder {
  constructor() {
    this.form = document.createElement('form');
  }

  addTextField(name, placeholder) {
    const input = document.createElement('input');
    input.type = 'text';
    input.name = name;
    input.placeholder = placeholder;
    this.form.appendChild(input);
    return this;
  }

  addEmailField(name, placeholder) {
    const input = document.createElement('input');
    input.type = 'email';
    input.name = name;
    input.placeholder = placeholder;
    this.form.appendChild(input);
    return this;
  }

  addSubmitButton(label) {
    const button = document.createElement('input');
    button.type = 'submit';
    button.value = label;
    this.form.appendChild(button);
    return this;
  }

  build() {
    return this.form;
  }
}

// 使用建造者模式创建表单
const form = new FormBuilder()
  .addTextField('username', 'Enter your username')
  .addEmailField('email', 'Enter your email')
  .addSubmitButton('Submit')
  .build();

// 将表单添加到DOM中
document.body.appendChild(form);

请求构建

在发送HTTP请求时,可能需要设置各种请求参数、头信息和请求体。通过建造者模式,可以更加灵活和清晰地构建复杂的HTTP请求。

class RequestBuilder {
  constructor() {
    this.url = '';
    this.method = 'GET';
    this.headers = {};
    this.body = null;
  }

  setURL(url) {
    this.url = url;
    return this;
  }

  setMethod(method) {
    this.method = method;
    return this;
  }

  addHeader(key, value) {
    this.headers[key] = value;
    return this;
  }

  setBody(body) {
    this.body = body;
    return this;
  }

  build() {
    return fetch(this.url, {
      method: this.method,
      headers: this.headers,
      body: JSON.stringify(this.body)
    });
  }
}

// 使用建造者模式创建请求
new RequestBuilder()
  .setURL('https://jsonplaceholder.typicode.com/posts')
  .setMethod('POST')
  .addHeader('Content-Type', 'application/json')
  .setBody({ title: 'foo', body: 'bar', userId: 1 })
  .build()
  .then(response => response.json())
  .then(json => console.log(json));

复杂对象创建

在需要创建具有多种可选配置项的复杂对象时,建造者模式可以将对象的创建步骤分离,使得创建过程更为清晰。

class User {
  public name: string;
  public email: string;
  public age?: number;
  public address?: string;

  constructor(builder: UserBuilder) {
    this.name = builder.name;
    this.email = builder.email;
    this.age = builder.age;
    this.address = builder.address;
  }
}

class UserBuilder {
  public name: string;
  public email: string;
  public age?: number;
  public address?: string;

  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }

  setAge(age: number): UserBuilder {
    this.age = age;
    return this;
  }

  setAddress(address: string): UserBuilder {
    this.address = address;
    return this;
  }

  build(): User {
    return new User(this);
  }
}

// 使用建造者模式创建User对象
const user = new UserBuilder('John Doe', '[email protected]')
  .setAge(30)
  .setAddress('123 Main St')
  .build();

console.log(user);

配置管理

在大型前端应用中,经常需要管理复杂的配置选项。通过建造者模式,可以灵活地创建和管理应用的配置。

class AppConfig {
  public apiUrl: string;
  public timeout: number;
  public debugMode: boolean;

  constructor(builder: AppConfigBuilder) {
    this.apiUrl = builder.apiUrl;
    this.timeout = builder.timeout;
    this.debugMode = builder.debugMode;
  }
}

class AppConfigBuilder {
  public apiUrl: string = 'http://localhost:3000';
  public timeout: number = 5000;
  public debugMode: boolean = false;

  setApiUrl(url: string): AppConfigBuilder {
    this.apiUrl = url;
    return this;
  }

  setTimeout(timeout: number): AppConfigBuilder {
    this.timeout = timeout;
    return this;
  }

  setDebugMode(debugMode: boolean): AppConfigBuilder {
    this.debugMode = debugMode;
    return this;
  }

  build(): AppConfig {
    return new AppConfig(this);
  }
}

// 使用建造者模式创建AppConfig对象
const config = new AppConfigBuilder()
  .setApiUrl('https://api.example.com')
  .setTimeout(10000)
  .setDebugMode(true)
  .build();

console.log(config);

组件库或SDK的封装

当你负责维护一个提供给其他开发者使用的组件库或SDK时,建造者模式可以提供一个清楚、简洁的API来构建或配置这些组件。 我们来构建一个通知系统的组件库,允许开发者通过建造者模式来创建不同类型(如信息、警告、错误)的通知,并配置通知的内容、样式和行为等属性。

// 通知组件接口
interface INotification {
  show(): void;
}

// 通知类
class Notification implements INotification {
  public type: string;
  public message: string;
  public duration: number;
  public dismissable: boolean;

  constructor(builder: NotificationBuilder) {
    this.type = builder.type;
    this.message = builder.message;
    this.duration = builder.duration;
    this.dismissable = builder.dismissable;
  }

  show(): void {
    // 这里简单实现一个展示通知的逻辑
    const notificationElement = document.createElement('div');
    notificationElement.className = `notification ${this.type}`;
    notificationElement.textContent = this.message;

    if (this.dismissable) {
      const closeButton = document.createElement('button');
      closeButton.textContent = 'x';
      closeButton.onclick = () => notificationElement.remove();
      notificationElement.appendChild(closeButton);
    }

    document.body.appendChild(notificationElement);

    if (this.duration > 0) {
      setTimeout(() => {
        notificationElement.remove();
      }, this.duration);
    }
  }
}

// 通知建造者类
class NotificationBuilder {
  // 默认值
  public type: string = 'info';
  public message: string = '';
  public duration: number = 3000;
  public dismissable: boolean = true;

  setType(type: string): NotificationBuilder {
    this.type = type;
    return this;
  }

  setMessage(message: string): NotificationBuilder {
    this.message = message;
    return this;
  }

  setDuration(duration: number): NotificationBuilder {
    this.duration = duration;
    return this;
  }

  setDismissable(dismissable: boolean): NotificationBuilder {
    this.dismissable = dismissable;
    return this;
  }

  build(): Notification {
    return new Notification(this);
  }
}

// 使用建造者模式创建通知组件
const notification = new NotificationBuilder()
  .setType('error')
  .setMessage('An error occurred!')
  .setDuration(5000)
  .setDismissable(true)
  .build();

// 显示通知
notification.show();

const successNotification = new NotificationBuilder()
  .setType('success')
  .setMessage('Operation successful!')
  .build();

// 显示成功通知
successNotification.show();

为了提供更好的用户体验,你可以对setTypesetMessagesetDuration方法增加更多验证和默认行为。

class NotificationBuilder {
  // 默认值
  public type: string = 'info';
  public message: string = '';
  public duration: number = 3000;
  public dismissable: boolean = true;

  setType(type: string): NotificationBuilder {
    const validTypes = ['info', 'success', 'warning', 'error'];
    if (!validTypes.includes(type)) {
      throw new Error("Invalid notification type");
    }
    this.type = type;
    return this;
  }

  setMessage(message: string): NotificationBuilder {
    if (!message) {
      throw new Error("Message cannot be empty");
    }
    this.message = message;
    return this;
  }

  setDuration(duration: number): NotificationBuilder {
    if (duration < 0) {
      throw new Error("Duration cannot be negative");
    }
    this.duration = duration;
    return this;
  }

  setDismissable(dismissable: boolean): NotificationBuilder {
    this.dismissable = dismissable;
    return this;
  }

  build(): Notification {
    return new Notification(this);
  }
}

// 测试包含验证的通知建造者
try {
  const invalidNotification = new NotificationBuilder().setType('danger').build();
} catch (error) {
  console.error(error.message); // 输出: Invalid notification type
}

try {
  const notificationWithEmptyMessage = new NotificationBuilder().setMessage('').build();
} catch (error) {
  console.error(error.message); // 输出: Message cannot be empty
}

const validNotification = new NotificationBuilder()
  .setType('success')
  .setMessage('Operation completed successfully!')
  .build();

validNotification.show();

最后,可以添加一些基本的CSS以确保通知显示的样式更加美观:

.notification {
  padding: 15px;
  margin: 10px;
  border-radius: 3px;
  color: #fff;
}

.notification.info {
  background-color: #2196F3;
}

.notification.success {
  background-color: #4CAF50;
}

.notification.warning {
  background-color: #FF9800;
}

.notification.error {
  background-color: #F44336;
}

.notification button {
  background: none;
  border: none;
  color: #fff;
  font-size: 1.2em;
  cursor: pointer;
  padding-left: 10px;
}

建造者模式的优缺点

优点

  1. 分离复杂对象的构造和表示:建造者模式将复杂对象的构造过程分离出来,使得生成不同表示的对象更加清晰、灵活。
  2. 逐步构造:可以逐步构造产品,可以灵活地选择需要的配件。
  3. 提高代码可读性:每个配置项的设置都有独立的方法调用,代码更加易读。

缺点

  1. 增加代码量:建造者模式引入了额外的 BuilderDirector 类,增加了代码量。
  2. 不适合简单对象:对于简单对象的创建,使用建造者模式显得过于复杂。

结论

建造者模式是一种强大的设计模式,通过将对象构造过程分步骤进行,使得复杂对象的创建变得更加灵活和清晰。虽然它在简单对象的创建中可能显得冗长,但在许多实际应用中,建造者模式提供了一个高效且易维护的解决方案。