Menu
CoddyTech

Nhúng và kết tập

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 38 trên 107.

Bạn đã học rằng nhúng giúp đưa các trường và phương thức ra struct bên ngoài. Go cũng hỗ trợ kết hợp, tức là thêm một struct khác làm trường có tên. Hiểu khi nào nên dùng mỗi cách là yếu tố then chốt để thiết kế tốt.

Với nhúng, bạn bỏ tên trường, và các thành viên của kiểu bên trong có thể được truy cập trực tiếp:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // Nhúng trực tiếp, không có tên trường
}

d := Document{}
d.Write()  // Truy cập trực tiếp

Với phép kết hợp (aggregation), bạn đặt tên tường minh cho trường, và cần dùng tên đó để truy cập kiểu bên trong:

type Document struct {
    writer Writer  // kết tập: trường có tên
}

d := Document{writer: Writer{}}
d.writer.Write()  // phải dùng tên trường

Điểm khác biệt chính nằm ở mối quan hệ mà bạn muốn thể hiện. Kết hợp nhúng ngụ ý mối quan hệ “là một”, trong đó kiểu bên ngoài mang danh tính của kiểu bên trong. Kết hợp chứa ngụ ý mối quan hệ “có một”, trong đó kiểu bên ngoài chỉ chứa kiểu bên trong.

Dùng nhúng (embedding) khi bạn muốn kiểu bên ngoài đáp ứng các giao diện mà kiểu bên trong triển khai, hoặc khi truy cập trực tiếp vào phương thức giúp API gọn hơn. Dùng kết tập (aggregation) khi kiểu bên trong là chi tiết triển khai không nên để lộ, hoặc khi bạn muốn kiểm soát rõ cách truy cập kiểu bên trong.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống tạo báo cáo để minh họa khi nào nên dùng nhúng và khi nào nên dùng kết tập. Bạn sẽ tạo một hệ thống trong đó một số thành phần có thể được truy cập trực tiếp (nhúng), còn những thành phần khác vẫn là chi tiết triển khai nội bộ (kết tập).

Bạn sẽ sắp xếp code trong ba tệp:

  • formatter.go: Tạo một struct Formatter có trường Style (chuỗi) và phương thức Format(text string) string trả về văn bản được bao quanh bởi kiểu định dạng, như [Style] text [/Style]. Đây sẽ là một thành phần hỗ trợ nội bộ, không nên để lộ trực tiếp.
  • report.go: Tạo hai struct xử lý việc tạo báo cáo theo những cách khác nhau:
    • Một struct Metadata có các trường Author và Date. Struct này đại diện cho thông tin công khai mà bạn có thể truy cập trực tiếp.
    • Một struct Report có trường Title, nhúng Metadata để truy cập trực tiếp tác giả và ngày tháng, nhưng dùng kết tập với Formatter như một chi tiết triển khai nội bộ qua trường có tên formatter.
    Thêm phương thức Generate() string vào Report. Phương thức này dùng formatter nội bộ để định dạng tiêu đề và thêm thông tin metadata.
  • main.go: Đọc thông tin báo cáo từ đầu vào, tạo một Report có metadata được nhúng và formatter được kết tập. Minh họa sự khác biệt bằng cách truy cập trực tiếp tác giả từ report (thông qua nhúng), rồi tạo báo cáo đã định dạng (sử dụng formatter ẩn bên trong).

Đầu vào sẽ gồm:

  • Dòng 1: Tiêu đề báo cáo
  • Dòng 2: Tên tác giả
  • Dòng 3: Ngày tháng
  • Dòng 4: Kiểu định dạng của formatter

Phương thức Format của bạn cần trả về:

[Style] text [/Style]

Phương thức Generate của bạn cần trả về:

[formatted title]
Author: [Author], Date: [Date]

Trong tệp chính, hãy in ra ba dòng:

  1. Tác giả được truy cập trực tiếp từ Report (minh họa cách nhúng giúp truy cập trực tiếp trường)
  2. Ngày tháng được truy cập thông qua tên kiểu dữ liệu được nhúng (report.Metadata.Date)
  3. Kết quả gọi Generate()

Ví dụ, với Quarterly Results, Jane Doe, 2024-01-15 và BOLD, đầu ra của bạn sẽ là:

Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15

Lưu ý rằng bạn có thể truy cập trực tiếp Author và Date từ Report thông qua nhúng, trong khi formatter vẫn được ẩn. Bạn không thể gọi trực tiếp report.Format() vì formatter được kết tập qua một trường có tên. Đây là ví dụ thực tế về mối quan hệ “là một” và “có một”.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	scanner.Scan()
	title := scanner.Text()
	
	scanner.Scan()
	author := scanner.Text()
	
	scanner.Scan()
	date := scanner.Text()
	
	scanner.Scan()
	style := scanner.Text()
	
	// TODO: Tạo struct Metadata chứa tác giả và ngày tháng
	
	// TODO: Tạo Formatter với kiểu định dạng đã cho
	
	// TODO: Tạo Report nhúng Metadata và chứa Formatter
	
	// TODO: In ra tác giả bằng cách truy cập trực tiếp qua Report (việc nhúng đưa trường này lên Report)
	
	// TODO: In ra ngày tháng bằng cách truy cập qua tên kiểu được nhúng (report.Metadata.Date)
	
	// TODO: In ra kết quả khi gọi Generate()
	
	// Ẩn cảnh báo về các biến chưa dùng (xóa các dòng này khi bạn sử dụng các biến đó)
	_ = title
	_ = author
	_ = date
	_ = style
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Go online