ข้ามไปยังเนื้อหา

Builder

Builder แยกการสร้าง object ที่ซับซ้อนออกจากการแทนค่าของตัวเอง ให้คุณประกอบ object ผ่านลำดับขั้นตอนที่ชัดเจน และผลิต product สำเร็จรูปออกมาเฉพาะตอนจบเท่านั้น

object บางตัวมีชิ้นส่วนเสริมจำนวนมาก HTTP request มี method, URL, header เสริม, body เสริม และ timeout การเข้ารหัสทุกการผสมผสานเป็น constructor นำไปสู่ทั้งรายการ overload ที่ยืดยาว หรือ constructor ยักษ์ตัวเดียวที่เต็มไปด้วย null และ boolean ที่คุณอ่านความหมายไม่ออก ณ จุดที่เรียกใช้ new Request("GET", url, null, null, 30, true, false) คือปริศนา ไม่ใช่ code

Builder แทนที่สิ่งนั้นด้วยขั้นตอนที่มีชื่อและต่อเชื่อมกันได้ คุณเริ่ม builder เรียกเฉพาะขั้นตอนที่คุณสนใจในลำดับใดก็ได้ และจบด้วย build() ตัวเดียวที่คืน product ที่ immutable และผ่านการตรวจสอบแล้ว จุดที่เรียกใช้อ่านได้เหมือนคำอธิบายสิ่งที่คุณต้องการ ตรรกะการสร้าง ทั้งค่าเริ่มต้น การตรวจสอบ และการประกอบ อยู่ใน builder แยกจากความรับผิดชอบของตัว product เอง

classDiagram
  class HttpRequest {
    +method: string
    +url: string
    +headers: Map
    +body: string
    +summary() string
  }
  class RequestBuilder {
    -method: string
    -url: string
    -headers: Map
    -body: string
    +setMethod(m) RequestBuilder
    +addHeader(k, v) RequestBuilder
    +setBody(b) RequestBuilder
    +build() HttpRequest
  }
  RequestBuilder ..> HttpRequest : builds
builder สะสมชิ้นส่วนต่าง ๆ แล้วปล่อย request ที่สำเร็จรูปออกมา
  • Product (HttpRequest) — object ที่ซับซ้อนซึ่งกำลังถูกประกอบขึ้น โดยทั่วไปจะ immutable เมื่อสร้างเสร็จ
  • Builder (RequestBuilder) — เปิดเผยหนึ่งขั้นตอนต่อหนึ่งชิ้นส่วน แต่ละขั้นคืน builder กลับมาเพื่อให้เรียกต่อกันได้ บวกกับ build() ตัวสุดท้ายที่ตรวจสอบและคืน product
  • Client — เรียกขั้นตอนที่ต้องการในลำดับใดก็ได้ แล้วจึง build()

Director (class ที่ขับเคลื่อนลำดับการเรียก builder แบบตายตัว) เป็นส่วนเสริมที่มีก็ได้ ไม่มีก็ได้ สำหรับ fluent builder อย่างตัวนี้ client เองเล่นบทบาทนั้น

RequestBuilder แบบ fluent ที่ผลิต HttpRequest ที่ immutable

class HttpRequest {
constructor(
readonly method: string,
readonly url: string,
readonly headers: Record<string, string>,
readonly body: string | null,
) {}
summary(): string {
const headerCount = Object.keys(this.headers).length;
return `${this.method} ${this.url} (${headerCount} headers, body: ${this.body !== null})`;
}
}
class RequestBuilder {
private method = 'GET';
private headers: Record<string, string> = {};
private body: string | null = null;
constructor(private readonly url: string) {}
setMethod(method: string): this {
this.method = method;
return this;
}
addHeader(key: string, value: string): this {
this.headers[key] = value;
return this;
}
setBody(body: string): this {
this.body = body;
return this;
}
build(): HttpRequest {
return new HttpRequest(this.method, this.url, this.headers, this.body);
}
}
const request = new RequestBuilder('https://api.example.com/items')
.setMethod('POST')
.addHeader('Content-Type', 'application/json')
.setBody('{"name":"book"}')
.build();
console.log(request.summary());
  • ข้อดี: จุดที่เรียกใช้อ่านได้ชัดเจน แต่ละขั้นตอนมีชื่อ ความหมายของทุกค่าจึงเห็นได้ชัด
  • ข้อดี: จัดการชิ้นส่วนเสริมจำนวนมากได้โดยไม่เกิดการระเบิดเชิงผสมของ constructor
  • ข้อดี: การตรวจสอบและค่าเริ่มต้นอยู่ในที่เดียว และ product สำเร็จรูปสามารถเป็น immutable ได้
  • ข้อเสีย: code มากกว่า constructor คุ้มก็ต่อเมื่อ object ซับซ้อนจริง ๆ
  • ข้อเสีย: builder ที่สร้างไปครึ่งทางอยู่ในสถานะที่ไม่สมบูรณ์ ดังนั้น build() ควรตรวจสอบ field ที่จำเป็น
  • Abstract Factory คืน product ในการเรียกครั้งเดียว ส่วน Builder สร้างผ่านหลายขั้นตอน
  • Prototype เป็นทางเลือกเมื่อมี object ที่ตั้งค่าไว้แล้วอยู่ และคุณต้องการสำเนา
BuilderFactory MethodPrototype
สร้างอะไรobject ที่มี config ซับซ้อนproduct เดี่ยวจาก subclassobject ที่ clone จากต้นแบบ
ขั้นตอนหลายขั้น เรียงตามลำดับขั้นเดียวขั้นเดียว (clone)
เมื่อใช้มี optional field จำนวนมากต้องการ defer การสร้างต้องการ copy object ที่ตั้งค่าแล้ว
ตัวอย่างใน frameworkStringBuilder, query builderLoggerFactory, RouterFactoryObject.create(), clone()
Builder จัดการกับปัญหาใดเป็นหลัก?
ทำไม method ขั้นตอนของ builder จึงมักคืน builder ตัวเองกลับมา?
บทบาทของ method build() ตัวสุดท้ายคืออะไร?