I. Proxy - Mẫu Thiết Kế Cấu Trúc
Mẫu thiết kế Proxy là một khái niệm quen thuộc trong lập trình, được sử dụng để đại diện cho một tài nguyên gốc. Proxy cung cấp các chức năng tương tự như tài nguyên thật nhưng với khả năng tùy biến cao.
Proxy thường được trách nhiệm là thay thế tài nguyên gốc, ẩn đi các chi tiết phức tạp và bổ sung các chức năng thiết yếu như kiểm soát truy cập, lưu trữ bộ nhớ đệm (caching), hoặc giảm tải xử lý cho hệ thống.
II. Ví Dụ Thực Tế
Trong nhiều hệ thống, việc truy vấn thông tin người dùng từ cơ sở dữ liệu chính diễn ra thường xuyên. Tuy nhiên, nếu truy vấn trực tiếp đến cơ sở dữ liệu, có thể dẫn đến độ trễ cao và giảm hiệu suất của hệ thống. Để giải quyết vấn đề này, chúng ta có thể áp dụng mẫu Proxy như một bộ đệm (cache) để lưu trữ tạm thời các dữ liệu thường xuyên được truy vấn.
Proxy sẽ kiểm tra bộ nhớ đệm trước. Nếu dữ liệu đã tồn tại trong bộ nhớ đệm, nó sẽ trả về ngay lập tức. Nếu không, Proxy sẽ thực hiện truy vấn từ cơ sở dữ liệu chính và sau đó cập nhật bộ nhớ đệm.
Trong trường hợp này, chúng ta sẽ mô tả cơ chế Proxy giúp tìm kiếm người dùng (User
) giữa MainDB
(cơ sở dữ liệu chính) và Stack
(bộ nhớ đệm) nhằm cải thiện hiệu suất hệ thống.
1. Định Nghĩa User
User là một thực thể đơn giản với thuộc tính ID
(kiểu int
), đại diện cho một đối tượng người dùng.
2. Interface Chung
Giao diện UserFinder
định nghĩa phương thức Find(ID int)
để tìm kiếm người dùng theo ID. Cả MainDB
và bộ nhớ đệm đều tuân theo giao diện này.
3. Cơ Sở Dữ Liệu Chính (MainDB
)
UsersDB
là nơi lưu trữ người dùng chính, cung cấp chức năng Find
để tìm kiếm và Add
để thêm người dùng mới.
4. Proxy (UserFinderProxy
)
- Là lớp trung gian sử dụng
Stack
(bộ nhớ đệm) để truy cập nhanh hơn trước khi tìm kiếm trongMainDB
. - Khi một người dùng không có trong
Stack
, Proxy sẽ kiểm traMainDB
. Nếu tìm thấy, Proxy sẽ lưu người dùng đó vàoStack
để tối ưu cho các lần truy xuất sau. Stack
có cơ chế giới hạn dung lượng và tự động cập nhật khi đầy.
Sơ Đồ Lớp
[Không có hình ảnh, chỉ cần đưa ra một sơ đồ lớp điển hình để trình bày cấu trúc mối quan hệ giữa các lớp trong thiết kế Proxy này]
III. Triển Khai
1. Định Nghĩa Type cho User Entity
go
package proxy
type User struct {
ID int
}
2. Định Nghĩa Interface Chung
go
package proxy
type UserFinder interface {
Find(ID int) (User, error)
}
3. Cơ Sở Dữ Liệu
go
package proxy
import "fmt"
type UsersDB []User
func (u *UsersDB) Find(ID int) (User, error) {
for _, user := range *u {
if user.ID == ID {
return user, nil
}
}
return User{}, fmt.Errorf("user not found")
}
func (u *UsersDB) Add(user User) *UsersDB {
fmt.Println("Adding to database: ", user)
*u = append(*u, user)
return u
}
4. Định Nghĩa Proxy
go
package proxy
import "fmt"
type UsersStack []User
func (u *UsersStack) Find(ID int) (User, error) {
for _, user := range *u {
if user.ID == ID {
return user, nil
}
}
return User{}, fmt.Errorf("user not found")
}
func (u *UsersStack) Add(user User) *UsersStack {
*u = append(*u, user)
return u
}
type UserFinderProxy struct {
MainDB UsersDB
Stack UsersStack
Capacity int
}
func (u *UserFinderProxy) Find(ID int) (User, error) {
user, err := u.Stack.Find(ID)
if err == nil {
fmt.Println("Found in stack: ", user)
return user, nil
}
user, err = u.MainDB.Find(ID)
if err != nil {
return User{}, err
}
fmt.Println("Found in mainDB: ", user)
u.AddToStack(user)
return user, nil
}
func (u *UserFinderProxy) AddToStack(user User) error {
fmt.Println("Adding to stack: ", user)
if len(u.Stack) >= u.Capacity {
u.Stack = append(u.Stack[1:], user)
} else {
u.Stack.Add(user)
}
return nil
}
Chạy Chương Trình
go
// Ví dụ về Proxy
fmt.Println("*** Ví Dụ Proxy ***")
mainDB := proxy.UsersDB{}
user1 := proxy.User{ID: 1}
user2 := proxy.User{ID: 2}
user3 := proxy.User{ID: 3}
mainDB.Add(user1).Add(user2).Add(user3)
proxy := proxy.UserFinderProxy{
MainDB: mainDB,
Stack: proxy.UsersStack{},
Capacity: 2,
}
proxy.Find(1)
proxy.Find(2)
proxy.Find(3)
proxy.Find(2)
proxy.Find(1)
fmt.Print("*** Kết Thúc Ví Dụ Proxy ***\n\n\n")
Kết Quả
[Không cần hình ảnh, chỉ cần mô tả kết quả kỳ vọng từ chương trình]
IV. Giải Thích Ví Dụ Proxy
Ví dụ này minh họa cách hoạt động của mẫu Proxy thông qua việc truy vấn người dùng (User) từ cơ sở dữ liệu chính (MainDB
) và sử dụng bộ nhớ đệm (Stack
) để tối ưu hóa các lần tìm kiếm lặp lại. Dưới đây là giải thích chi tiết từng bước trong quá trình khai thác:
1. Khởi Tạo Cơ Sở Dữ Liệu Chính MainDB
- Khởi tạo cơ sở dữ liệu chứa danh sách các người dùng với 3 người dùng có
ID = 1, 2, 3
. Mọi truy vấn đều có thể tìm thấy các User này từMainDB
.
2. Khởi Tạo Proxy với MainDB
và Stack
- Tạo một Proxy với
MainDB
,Stack
và xác định khả năng lưu trữ tối đa (Capacity).
3. Tìm Kiếm User
- Lần 1: Tìm
ID = 1
, không thấy trongStack
, tìm trongMainDB
, tìm thấy và thêm vàoStack
. - Lần 2: Tìm
ID = 2
, không thấy trongStack
, tìm trongMainDB
, tìm thấy và thêm vàoStack
. - Lần 3: Tìm
ID = 3
, không thấy trongStack
, tìm trongMainDB
, tìm thấy và thêm vào nhưng doStack
đã đầy nên loại bỏ User lâu nhất. - Lần 4: Tìm
ID = 2
, tìm thấy ngay trongStack
mà không cần truy vấnMainDB
. - Lần 5: Tìm
ID = 1
, không thấy trongStack
, truy vấn trongMainDB
, tìm thấy và thêm vào nhưng loại bỏ User lâu nhất (FIFO).
4. Kết Thúc Ví Dụ
- Cuối cùng,
Stack
chứa các User mới nhất được tìm kiếm, giúp giảm thiểu số lượng truy cập đếnMainDB
và cải thiện hiệu suất hệ thống.
V. Lời Kết
Không phải lúc nào cũng cần áp dụng mẫu Proxy cho mọi bài toán. Trong nhiều trường hợp, truy cập trực tiếp vào cơ sở dữ liệu là đủ nếu không cần tối ưu hóa hiệu suất. Hãy lưu ý rằng các giải pháp như mẫu Decorator cũng có thể phù hợp với một số tình huống cụ thể. Quan trọng là lựa chọn giải pháp tối ưu cho vấn đề của bạn. Cảm ơn các bạn đã theo dõi bài viết!
VI. Tài Liệu Tham Khảo
- Go Design Patterns (Mario Castro Contreras)
- Mã nguồn hoàn chỉnh về mẫu thiết kế Golang: tại đây.
source: viblo