Mẫu Composite
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 96 trên 107.
Mẫu Composite cho phép bạn xử lý các đối tượng riêng lẻ và nhóm đối tượng theo cùng một cách. Trong khi State thay đổi hành vi dựa trên trạng thái bên trong, Composite xây dựng cấu trúc cây, trong đó cả nút lá và nút chứa đều dùng chung một giao diện.
Mẫu này rất phù hợp với các cấu trúc phân cấp như hệ thống tệp, sơ đồ tổ chức hoặc thành phần giao diện người dùng. Trong Go, chúng ta định nghĩa một giao diện chung mà cả các mục riêng lẻ lẫn vùng chứa đều triển khai:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}Đối tượng tổng hợp (vùng chứa) chứa các đối tượng con và triển khai cùng một giao diện bằng cách tổng hợp kết quả của chúng:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Giờ bạn có thể lồng các thư mục bên trong thư mục khác, và gọi GetSize() sẽ hoạt động giống hệt nhau dù đó là một tệp đơn lẻ hay cả một cây thư mục:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Composite phù hợp khi bạn cần biểu diễn cấu trúc phân cấp giữa tổng thể và bộ phận, đồng thời muốn phía sử dụng xử lý các đối tượng riêng lẻ và các tổ hợp theo cùng một cách.
Thử thách
DễHãy xây dựng hệ thống sơ đồ tổ chức bằng mẫu Composite! Bạn sẽ tạo một cấu trúc trong đó cả nhân viên riêng lẻ lẫn phòng ban, gồm nhân viên hoặc phòng ban khác, đều có thể được xử lý thống nhất. Cách này rất phù hợp để tính tổng lương ở bất kỳ bộ phận nào trong tổ chức.
Bạn sẽ sắp xếp code trong ba tệp:
component.go: Định nghĩa giao diện chung mà cả cá nhân lẫn nhóm đều sẽ triển khai.Tạo giao diện
OrgComponentvới hai phương thức:GetSalary() int: trả về tổng lương của thành phần nàyGetName() string: trả về tên nhân viên hoặc phòng ban
organization.go: Triển khai cả kiểu nút lá (nhân viên riêng lẻ) và kiểu tổ hợp (phòng ban).Tạo cấu trúc
Employeevới các trườngName(string) vàSalary(int).GetSalary()trả về mức lương của chính nhân viên đó, cònGetName()trả về tên của họ.Tạo cấu trúc
Departmentvới trườngName(string) và một lát cắtMemberschứa các phần tửOrgComponent. Thêm phương thứcAdd(c OrgComponent)để thêm thành viên.GetSalary()sẽ trả về tổng lương của tất cả thành viên, cònGetName()trả về tên phòng ban.main.go: Xây dựng cấu trúc tổ chức và tính lương.Đọc số lượng nhân viên. Với mỗi nhân viên, đọc tên và mức lương của họ để tạo các instance
Employee. Sau đó, đọc số lượng phòng ban. Với mỗi phòng ban, đọc tên và số lượng chỉ số thành viên, rồi đọc các chỉ số đó (bắt đầu từ 0, tham chiếu đến nhân viên hoặc phòng ban đã tạo trước đó theo thứ tự tạo).Sau khi xây dựng cấu trúc, đọc chỉ số cuối cùng và in tên cùng tổng lương của thành phần đó theo định dạng:
[name]: [salary]
Đầu vào sẽ gồm:
- Số lượng nhân viên, sau đó là tên và mức lương của từng nhân viên trên các dòng riêng
- Số lượng phòng ban, sau đó là tên, số lượng thành viên và các chỉ số thành viên của từng phòng ban
- Chỉ số cuối cùng cần truy vấn
Ví dụ, với đầu vào:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Đầu ra cần là:
Engineering: 165000Với đầu vào:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Đầu ra cần là:
QA: 125000Với đầu vào:
2
Alice
50000
Bob
60000
0
1Đầu ra cần là:
Bob: 60000Lưu ý rằng GetSalary() hoạt động giống hệt nhau dù bạn gọi phương thức này trên một nhân viên riêng lẻ hay cả phòng ban: mẫu Composite cho phép bạn xử lý cá nhân và nhóm thống nhất thông qua cùng một giao diện!
Tự làm thử
package main
import "fmt"
func main() {
// Đọc số lượng nhân viên
var numEmployees int
fmt.Scanln(&numEmployees)
// Lưu tất cả thành phần (nhân viên và phòng ban) theo thứ tự tạo
var components []OrgComponent
// TODO: Đọc tên và lương của từng nhân viên
// Tạo các instance Employee và thêm vào slice components
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Tạo nhân viên và thêm vào components
}
// Đọc số lượng phòng ban
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Đọc tên, số lượng thành viên và chỉ số thành viên của từng phòng ban
// Tạo các instance Department, thêm thành viên theo chỉ số rồi thêm vào components
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Tạo phòng ban, đọc chỉ số thành viên, thêm thành viên rồi thêm vào components
}
// Đọc chỉ số cuối cùng cần truy vấn
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: In ra tên và tổng lương của thành phần theo định dạng: [name]: [salary]
// Ví dụ: fmt.Printf("%s: %d\n", name, salary)
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp11Thư viện chuẩn và OOP
io.Reader và io.Writersort.Interfaceinterface fmt.Stringerencoding/json với structinterface http.HandlerÔn tập: mô hình REST API14Mẫu thiết kế phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMiddleware dưới dạng Decorator3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online