Tôi quen thuộc với thực tế rằng, trong Go, các giao diện xác định chức năng, thay vì dữ liệu. Bạn đặt một tập hợp các phương thức vào một giao diện, nhưng bạn không thể chỉ định bất kỳ trường nào sẽ được yêu cầu trên bất kỳ thứ gì triển khai giao diện đó.
Ví dụ:
// Interface
type Giver interface {
Give() int64
}
// One implementation
type FiveGiver struct {}
func (fg *FiveGiver) Give() int64 {
return 5
}
// Another implementation
type VarGiver struct {
number int64
}
func (vg *VarGiver) Give() int64 {
return vg.number
}
Bây giờ chúng ta có thể sử dụng giao diện và các triển khai của nó:
// A function that uses the interface
func GetSomething(aGiver Giver) {
fmt.Println("The Giver gives: ", aGiver.Give())
}
// Bring it all together
func main() {
fg := &FiveGiver{}
vg := &VarGiver{3}
GetSomething(fg)
GetSomething(vg)
}
/*
Resulting output:
5
3
*/
Bây giờ, những gì bạn không thể làm là một cái gì đó như thế này:
type Person interface {
Name string
Age int64
}
type Bob struct implements Person { // Not Go syntax!
...
}
func PrintName(aPerson Person) {
fmt.Println("Person's name is: ", aPerson.Name)
}
func main() {
b := &Bob{"Bob", 23}
PrintName(b)
}
Tuy nhiên, sau khi thử nghiệm với các giao diện và cấu trúc nhúng, tôi đã phát hiện ra một cách để làm điều này, sau một thời gian:
type PersonProvider interface {
GetPerson() *Person
}
type Person struct {
Name string
Age int64
}
func (p *Person) GetPerson() *Person {
return p
}
type Bob struct {
FavoriteNumber int64
Person
}
Do cấu trúc nhúng, Bob có mọi thứ mà Person có. Nó cũng triển khai giao diện PersonProvider, vì vậy chúng ta có thể chuyển Bob vào các hàm được thiết kế để sử dụng giao diện đó.
func DoBirthday(pp PersonProvider) {
pers := pp.GetPerson()
pers.Age += 1
}
func SayHi(pp PersonProvider) {
fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}
func main() {
b := &Bob{
5,
Person{"Bob", 23},
}
DoBirthday(b)
SayHi(b)
fmt.Printf("You're %v years old now!", b.Age)
}
Đây là một sân chơi cờ vây minh họa đoạn mã trên.
Sử dụng phương pháp này, tôi có thể tạo một giao diện xác định dữ liệu hơn là hành vi và có thể được thực hiện bởi bất kỳ cấu trúc nào chỉ bằng cách nhúng dữ liệu đó. Bạn có thể xác định các hàm tương tác rõ ràng với dữ liệu nhúng đó và không biết về bản chất của cấu trúc bên ngoài. Và mọi thứ được kiểm tra tại thời điểm biên dịch! (Cách duy nhất bạn có thể làm rối tung, mà tôi có thể thấy, là nhúng giao diện PersonProvider
vào Bob
, thay vì một giao diện cụ thể Person
. Nó sẽ biên dịch và thất bại trong thời gian chạy.)
Bây giờ, đây là câu hỏi của tôi: đây là một mẹo nhỏ hay tôi nên làm theo cách khác?