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?
Jawaban:
Ini pasti trik yang bagus. Namun, mengungkap petunjuk tetap membuat akses langsung ke data tersedia, jadi itu hanya memberi Anda fleksibilitas tambahan terbatas untuk perubahan di masa mendatang. Selain itu, konvensi Go tidak mengharuskan Anda untuk selalu meletakkan abstraksi di depan atribut data Anda .
Mengambil hal-hal tersebut bersama-sama, saya akan cenderung ke satu ekstrim atau yang lain untuk kasus penggunaan tertentu: baik a) hanya membuat atribut publik (menggunakan embedding jika berlaku) dan meneruskan tipe konkret atau b) jika tampaknya mengekspos data akan menyebabkan masalah nanti, tampilkan pengambil / penyetel untuk abstraksi yang lebih kuat.
Anda akan menimbang ini pada basis per atribut. Misalnya, jika beberapa data spesifik untuk implementasi atau Anda berharap untuk mengubah representasi karena alasan lain, Anda mungkin tidak ingin mengekspos atribut secara langsung, sedangkan atribut data lainnya mungkin cukup stabil sehingga menjadikannya publik adalah keuntungan bersih.
Menyembunyikan properti di balik getter dan setter memberi Anda beberapa fleksibilitas ekstra untuk membuat perubahan yang kompatibel dengan mundur nanti. Katakanlah Anda suatu hari ingin mengubah
Person
untuk menyimpan tidak hanya satu bidang "nama" tetapi juga depan / tengah / terakhir / awalan; jika Anda memiliki metodeName() string
danSetName(string)
, Anda dapat membuat penggunaPerson
antarmuka yang ada senang sambil menambahkan metode baru yang lebih terperinci. Atau Anda mungkin ingin menandai objek yang didukung database sebagai "kotor" jika ada perubahan yang belum disimpan; Anda dapat melakukannya ketika pembaruan data semua melaluiSetFoo()
metode.Jadi: dengan getter / setter, Anda dapat mengubah bidang struct sambil mempertahankan API yang kompatibel, dan menambahkan logika di sekitar get / set properti karena tidak ada yang bisa melakukannya
p.Name = "bob"
tanpa melalui kode Anda.Fleksibilitas tersebut lebih relevan jika jenisnya rumit (dan basis kodenya besar). Jika Anda memiliki
PersonCollection
, mungkin secara internal didukung olehsql.Rows
, a[]*Person
,[]uint
ID database, atau apa pun. Menggunakan antarmuka yang tepat, Anda dapat menyelamatkan penelepon dari kepedulian, caraio.Reader
membuat koneksi jaringan dan file terlihat sama.Satu hal yang spesifik:
interface
s di Go memiliki properti khas yang bisa Anda implementasikan tanpa mengimpor paket yang mendefinisikannya; yang dapat membantu Anda menghindari impor siklik . Jika antarmuka Anda mengembalikan a*Person
, bukan hanya string atau apa pun, semuaPersonProviders
harus mengimpor paket tempatPerson
yang ditentukan. Itu mungkin bagus atau bahkan tidak bisa dihindari; itu hanya konsekuensi yang perlu diketahui.Tapi sekali lagi, komunitas Go tidak memiliki konvensi yang kuat terhadap pemaparan anggota data di API publik tipe Anda . Ini tersisa untuk penilaian Anda apakah itu masuk akal untuk menggunakan akses masyarakat terhadap atribut sebagai bagian dari API Anda dalam kasus tertentu, bukan mengecilkan setiap paparan karena mungkin bisa menyulitkan atau mencegah perubahan pelaksanaan nanti.
Jadi, misalnya, stdlib melakukan hal-hal seperti membiarkan Anda menginisialisasi an
http.Server
dengan konfigurasi Anda dan menjanjikan bahwa nolbytes.Buffer
dapat digunakan. Tidak apa-apa untuk melakukan hal-hal Anda sendiri seperti itu, dan, memang, saya tidak berpikir Anda harus mengabstraksikan hal-hal terlebih dahulu jika versi yang lebih konkret dan mengekspos data tampaknya akan berhasil. Ini hanya tentang menyadari pengorbanan.sumber
Jika saya benar-benar memahami Anda ingin mengisi satu bidang struct ke yang lain. Pendapat saya untuk tidak menggunakan antarmuka untuk memperluas. Anda dapat dengan mudah melakukannya dengan pendekatan selanjutnya.
https://play.golang.org/p/aBJ5fq3uXtt
Catatan
Person
dalamBob
deklarasi. Ini akan membuat bidang struct yang disertakan tersedia dalamBob
struktur secara langsung dengan beberapa gula sintaksis.sumber