Golang Design Patterns - Bridge. Tách bạch abstraction và implementation
Bridge là một mẫu thiết kế gây khó hiểu hơn so với các mẫu thiết kế ở những series trước. Theo cuốn sách nguyên bản của cuốn sách Gang of Four: "decouple an abstraction from its implementation so that the two can vary independently", khác hoàn toàn với việc luôn cố gắng đưa về abstraction trong các mẫu thiết kế chúng ta từng biết. Cùng tìm hiểu sâu hơn về mẫu thiết kế này nhé.
I. Bridge - Structural Pattern
Rõ hơn một chút, Bridge tách một abstraction (thứ mà hầu hết các design pattern khác đưa về) hay một đối tượng ra khỏi implementations của nó. Bằng cách này, chúng ta có thể khiến một đối tượng làm bất cứ những gì chúng ta muốn, hay thay đổi một đối tượng trừu tượng nhưng vẫn sử dụng lại được những implementations của nó.
II. Bridge mang lại cho developers những gì?
Dễ thấy nhất đó là sự linh động mà mẫu thiết kế này mang lại đối với những đối tượng, struct thường xuyên có sự cập nhật, thêm mới. Cũng không hề có sự ràng buộc khi thay đổi code giữa 2 phía định nghĩa đối tượng và triển khai.
III. Ví dụ thực tế
Lấy ví dụ thực tế với trường hợp chúng ta cần triển khai một hệ thống quản lý in ấn, bao gồm 2 đối tượng chính là máy tính và máy in. Mặc định với máy tính ta có 2 đối tượng MacOS và Window, với máy in là Canon, ta có thể triển khai các đối tượng sau: MacOSCanon và WindowCanon. Mỗi đối tượng đều có phương thức Print
, in ra thông tin máy tính và máy in hiện tại đang dùng.
Trường hợp bổ sung thêm loại máy in Epson, ta sẽ cần bổ sung thêm 2 đối tượng MacOSEpson và WindowEpson, với toàn bộ các phương thức mà MacOSCanon hay WindowCanon có thể làm.
Có thể thấy, với số lượng X
máy tính và Y
máy in, thì các đối tượng ta phải triển khai là X*Y
, rất nhiều đúng không.
Đây chính là lúc mà Bridge nhảy vào để xử lý vấn đề trên. Như mình nói ở đầu bài, các để ghi nhớ bản chất của Bridge là: tách bạch abstraction
và implementation
. Ứng dụng vào ví dụ, thì máy tính được xem như là abstraction
, còn máy in là implementation
. Hai đối tượng này giao tiếp với nhau thông qua Bridge
, nơi mà những abstraction (máy tính) chứa tham chiếu đến implementation (máy in).
Một ví dụ khá hài hước về Bridge rất dễ nhớ mà mình sưu tầm từ trang của Guru:
IV. Implementation
Các đối tượng máy in luôn có phương thức Print
, ta cần định nghĩa nó đầu tiên, thông qua interface Printer
package bridge
type Printer interface {
Print() error
}
Tiếp đến, định nghĩa các Printer HP và Epson tương ứng dựa trên interface Printer như sau:
package bridge
import "fmt"
type HP struct {
}
func (p *HP) Print() error {
fmt.Println("HP printer printing...")
return nil
}
package bridge
import "fmt"
type Epson struct {
}
func (p *Epson) Print() error {
fmt.Println("Epson printer printing...")
return nil
}
Đã xong phần định nghĩa phần Implementation
, bây giờ đến mục định nghĩa phần Abstraction
, bắt đầu với Computer
:
package bridge
type Computer interface {
Print() error
SetPrinter(printer Printer)
}
Như mình có nói ở trên, abstraction sẽ chứa tham chiếu đến máy in, chính vì thế các computer luôn chứa instance của máy in, phương thức SetPrinter như một cách linh động để chúng ta thay đổi máy in mà đối tượng máy tính sử dụng:
package bridge
import "fmt"
type Window struct {
printer Printer
}
func (w *Window) Print() error {
fmt.Println("Window printing...")
return w.printer.Print()
}
func (w *Window) SetPrinter(printer Printer) {
w.printer = printer
}
package bridge
import "fmt"
type MacOS struct {
printer Printer
}
func (m *MacOS) Print() error {
fmt.Println("MacOS printing...")
return m.printer.Print()
}
func (m *MacOS) SetPrinter(printer Printer) {
m.printer = printer
}
Chạy chương trình trên:
/*
Example Bridge
*/
fmt.Println("*** Example Bridge ***")
window := bridge.Window{}
macOS := bridge.MacOS{}
epson := bridge.Epson{}
hp := bridge.HP{}
window.SetPrinter(&epson)
window.Print()
macOS.SetPrinter(&hp)
macOS.Print()
fmt.Print("*** End of Bridge ***\n\n\n")
Với kết quả:
Mình đã đưa ra một ví dụ ở mức cơ bản nhất để mọi người có cái nhìn trực quan nhất về mẫu thiết kế Bridge. Chúng ta sẽ không bị vướng bận khi xoá đi một trong các đối tượng máy in hay máy tính, hay không phải đối mặt với công việc định nghĩa lặp đi lặp lại khi thêm một loại máy tính hay máy in chẳng hạn.
V. Lời kết
Không phải lúc nào gặp vấn đề như trên chúng ta luôn dùng mẫu Bridge để giải quyết bài toán, mọi người có thể sử dụng mẫu thiết Factory mà mình từng để cập chẳng hạn, hoặc là...không sử dụng bất kỳ mẫu thiết kế nào nếu bài toán không quá phức tạp về số lượng đối tượng. Tất cả là tuỳ thuộc vào bạn 😄
Cảm ơn các bạn đã xem bài viết.
VI. References
- Go Design Patterns (Mario Castro Contreras)
- Guru
All rights reserved