Giới thiệu về Generics
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 67 trên 107.
Trước Go 1.18, để viết code có thể tái sử dụng cho nhiều kiểu dữ liệu khác nhau, bạn phải sao chép hàm hoặc dùng giao diện rỗng any cùng với các phép khẳng định kiểu. Generics giải quyết vấn đề này bằng cách cho phép bạn viết hàm và kiểu dữ liệu hoạt động với nhiều kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu hoàn toàn.
Hãy thử tìm giá trị nhỏ nhất trong một lát cắt. Nếu không dùng kiểu tổng quát, bạn sẽ cần các hàm riêng cho từng kiểu dữ liệu:
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}Với generic, bạn chỉ cần viết logic một lần bằng cách dùng tham số kiểu trong dấu ngoặc vuông:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}[T int | float64] khai báo tham số kiểu T, có thể là int hoặc float64. Giờ đây, bạn có thể gọi Min([]int{3, 1, 2}) hoặc Min([]float64{3.5, 1.2}) bằng cùng một hàm. Trình biên dịch kiểm tra kiểu dữ liệu tại thời điểm biên dịch, nên bạn có được tính an toàn mà không tốn thêm chi phí khi chạy chương trình.
Trong các bài học tiếp theo, chúng ta sẽ tìm hiểu chi tiết về tham số kiểu, các ràng buộc và struct generic.
Thử thách
DễHãy xây dựng một chương trình phân tích điểm số có thể xử lý cả điểm số nguyên lẫn số thực bằng kiểu tổng quát! Bạn sẽ tạo một hàm tổng quát duy nhất để tìm giá trị lớn nhất trong một lát cắt, tránh phải viết mã trùng lặp cho các kiểu số khác nhau.
Bạn sẽ sắp xếp code trong hai tệp:
analyzer.go: Định nghĩa hàm phân tích tổng quát.Tạo hàm tổng quát
Max[T int | float64](values []T) Tđể tìm và trả về giá trị lớn nhất trong một lát cắt. Hàm này nên xử lý được cả kiểuintlẫnfloat64thông qua tham số kiểuT.Đồng thời, tạo hàm tổng quát
Sum[T int | float64](values []T) Tđể tính và trả về tổng tất cả giá trị trong một lát cắt.main.go: Đọc đầu vào và minh họa các hàm tổng quát với những kiểu dữ liệu khác nhau.Đọc chỉ báo kiểu dữ liệu (
inthoặcfloat), sau đó đọc số lượng, rồi đọc số giá trị tương ứng. Phân tích các giá trị theo kiểu dữ liệu, gọi cảMaxlẫnSumvới kiểu dữ liệu thích hợp, rồi in kết quả.Với đầu vào là số nguyên, hãy in:
Max (int): [value] Sum (int): [value]Với đầu vào là số thực, hãy in kết quả với một chữ số thập phân:
Max (float): [value] Sum (float): [value]
Đầu vào sẽ có dạng sau:
- Dòng 1: Chỉ báo kiểu dữ liệu (
inthoặcfloat) - Dòng 2: Số lượng giá trị (số nguyên)
- Các dòng tiếp theo: Mỗi dòng có một giá trị
Ví dụ, với đầu vào:
int
4
15
8
23
11Kết quả đầu ra sẽ là:
Max (int): 23
Sum (int): 57Và với đầu vào:
float
3
4.5
9.2
6.8Kết quả đầu ra sẽ là:
Max (float): 9.2
Sum (float): 20.5Lưu ý rằng cùng một hàm tổng quát có thể xử lý cả dữ liệu số nguyên lẫn số thực: tham số kiểu [T int | float64] cho phép trình biên dịch tạo code an toàn về kiểu dữ liệu cho mỗi lần sử dụng, trong khi bạn chỉ cần viết logic một lần.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc giá trị xác định kiểu dữ liệu
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Đọc số lượng
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Đọc các giá trị số nguyên
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: Gọi Max và Sum với lát cắt số nguyên
// TODO: In kết quả theo định dạng "Max (int): [value]" và "Sum (int): [value]"
} else if typeStr == "float" {
// Đọc các giá trị số thực
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: Gọi Max và Sum với lát cắt float64
// TODO: In kết quả với một chữ số thập phân theo định dạng "Max (float): [value]" và "Sum (float): [value]"
}
}
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ệp3Con 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