[Design Pattern] Builder Pattern
Mục lục
- Phần 1: Factory Method
- Phần 2: Abstract Factory
- Phần 3: Builder
- Phần 4: Prototype
- Phần 5: Singleton
- Phần 6: Adapter
- Phần 7: Bridge
Phần 3: Builder
Builder cho phép chúng ta có thể tạo ra các objects phức tạp theo từng bước một. Pattern cũng cho phép chúng ta có thể tạo các kiểu objects khác nhau sử dụng cùng một source code.
Vấn đề
Hãy tưởng tượng việc chúng ta phải viết các constructor code dài ngoằng, với nhiều dòng code và tham số. Đây là một điều vô cùng tồi tệ khi bạn phải maintain mớ bòng bong đó.
Ví dụ thực tế là khi xây nhà. Bạn cần
- Xây tường
- Xây sàn
- Xây mái
- …
Nhưng nếu bạn muốn ngôi nhà của mình to hơn, sáng hơn với sân sau hoặc hệ thống sưởi cho mùa đông ? Giải pháp đơn giản nhất đó là kế thừa từ House
class, sau đó tạo các subclasses để giải quyết hết các nhu cầu trên. Tuy nhiên điều mà bạn nhận lại được đó là một đống các subclasses đi kèm. Cứ khi nào có parameter mới là một lần bạn phải tạo thêm một subclass mới nữa.
Một giải pháp khác nữa đó là tạo một constructor khổng lồ, đáp ứng mọi parameters cho House
base class. Cách làm này giúp giảm đi đáng kể số lượng các subclasses, nhưng nó lại tạo ra thêm các vấn đề khác đi kèm.
Đa phần trong mọi trường hợp, chúng ta sẽ không sử dụng mọi parameters. Điều này vô tình dẫn đến việc các lời gọi hàm trông sẽ rất xấu.
Giải pháp
Tách construction code ra khỏi class và đưa nó đến các objects khác gọi là các builders
.
Pattern này sẽ tổ chức quá trình tạo object thành các bước (buildWalls
, buildDoor
, …). Để tạo ra object bạn chỉ cần gọi đến các hàm mà mình cần (tương ứng với đặc tả object mà bạn mong muốn).
Có những lúc các bước xây dựng object sẽ được implement theo những cách khác nhau (với cabin thì buildWalls
sẽ sử dụng gỗ, còn với nhà thông thường buildWalls
sẽ sử dụng gạch).
Trong những trường hợp như vậy bạn có thể tạo các classes khác nhau để triển khai tập hợp các builder methods
theo những cách khác nhau. Khi đó quá trình tạo một object sẽ là tập hợp lời gọi các builder methods
.
Director
Director Class
định nghĩa các bước để thực thi building steps, trong khi builder sẽ cung cấp implementation cho các bước này. Phía client không nhất thiết phải sử dụng Director Class
mà hoàn toàn có thể gọi các builder methods theo trình tự mà mình mong muốn.
Tuy nhiên Director Class
có thể là nơi đặt các construction routines để bạn có thể tái sử dụng chúng. Ngoài ra thì Director Class
cũng giúp chúng ta có thể che dấu đi các bước tạo ra object, client chỉ đơn thuần gọi đến Director Class
và lấy về object mong muốn.
Cấu trúc
- Builder interface
- Các class implement Builder interface
- Products: các result object
- Client phải truyền
Builder
vào choDirector
Source code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
interface Builder {
producePartA(): void;
producePartB(): void;
producePartC(): void;
}
class ConcreteBuilder1 implements Builder {
private product: Product1;
constructor() {
this.reset();
}
public reset(): void {
this.product = new Product1();
}
public producePartA(): void {
this.product.parts.push("PartA1");
}
public producePartB(): void {
this.product.parts.push("PartB1");
}
public producePartC(): void {
this.product.parts.push("PartC1");
}
public getProduct(): Product1 {
const result = this.product;
this.reset();
return result;
}
}
class Product1 {
public parts: string[] = [];
public listParts(): void {
console.log(`Product parts: ${this.parts.join(", ")}\n`);
}
}
class Director {
private builder: Builder;
constructor(builder: Builder) {
this.builder = builder;
}
public buildMinimalViableProduct(): void {
this.builder.producePartA();
}
public buildFullFeaturedProduct(): void {
this.builder.producePartA();
this.builder.producePartB();
this.builder.producePartC();
}
}
function clientCode() {
const builder = new ConcreteBuilder1();
const director = new Director(builder);
console.log("Standard basic product:");
director.buildMinimalViableProduct();
builder.getProduct().listParts();
console.log("Standard full featured product:");
director.buildFullFeaturedProduct();
builder.getProduct().listParts();
// Use builder pattern without director class
console.log("Custom product:");
builder.producePartA();
builder.producePartC();
builder.getProduct().listParts();
}
clientCode();