Saya akrab dengan fakta bahwa, di Go, antarmuka menentukan fungsionalitas, bukan data. Anda meletakkan sekumpulan metode ke dalam antarmuka, tetapi Anda tidak dapat menentukan bidang apa pun yang akan diperlukan pada apa pun yang mengimplementasikan antarmuka itu.
Sebagai contoh:
// 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
}
Sekarang kita dapat menggunakan antarmuka dan implementasinya:
// 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
*/
Sekarang, yang tidak dapat Anda lakukan adalah seperti ini:
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)
}
Namun, setelah bermain-main dengan antarmuka dan struct tertanam, saya telah menemukan cara untuk melakukan ini, setelah mode:
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
}
Karena struct tertanam, Bob memiliki semua yang dimiliki Orang. Ini juga mengimplementasikan antarmuka PersonProvider, sehingga kita dapat meneruskan Bob ke dalam fungsi yang dirancang untuk menggunakan antarmuka itu.
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)
}
Berikut adalah Go Playground yang mendemonstrasikan kode di atas.
Dengan menggunakan metode ini, saya dapat membuat antarmuka yang mendefinisikan data daripada perilaku, dan yang dapat diimplementasikan oleh struct apa pun hanya dengan menyematkan data itu. Anda dapat mendefinisikan fungsi yang secara eksplisit berinteraksi dengan data yang disematkan dan tidak mengetahui sifat dari struct luar. Dan semuanya diperiksa pada waktu kompilasi! (Satu-satunya cara Anda bisa mengacaukan, bahwa saya bisa melihat, akan embedding antarmuka PersonProvider
di Bob
, daripada beton Person
. Ini akan mengkompilasi dan gagal pada saat runtime.)
Sekarang, inilah pertanyaan saya: apakah ini trik yang rapi, atau haruskah saya melakukannya secara berbeda?