Interface bir metot kümesidir
Bir interface tipi metot imzalarını listeler. Bu metotlara sahip her tip, bunu söyleyen bir bildirim olmadan interface'i karşılar.
Ne Rect ne de Circle Shape'ten bahseder. Bu örtük gerçekleştirmedir ve Go interface'lerinin belirleyici özelliğidir. Bu, başka paketlerdeki tiplerin zaten karşıladığı bir interface'i, onların koduna dokunmadan kendi paketinizde tanımlayabileceğiniz anlamına gelir.
Standart kütüphaneden küçük interface'ler
Go kodu bir ya da iki metotlu interface'leri tercih eder. En önemlileri:
| Interface | Metot | Kullanan |
|---|---|---|
fmt.Stringer | String() string | fmt ile yazdırma |
error | Error() string | başarısız olabilecek her fonksiyon |
io.Reader | Read(p []byte) (n int, err error) | dosyalar, ağ, gzip, HTTP gövdeleri |
io.Writer | Write(p []byte) (n int, err error) | dosyalar, buffer'lar, hash'ler, HTTP yanıtları |
sort.Interface | Len, Less, Swap | sort paketi |
http.Handler | ServeHTTP(w, r) | net/http |
io.Reader'ın tek bir metodu olduğu için onlarca tip onu gerçekleştirir ve bir io.Reader alan her fonksiyon hepsiyle çalışır:
Go atasözü şöyle der: "interface ne kadar büyükse soyutlama o kadar zayıftır". Daha büyük interface'ler küçükleri birleştirerek kurulur: io.ReadWriter, bir interface'i diğerine gömerek yazılmış Reader artı Writer'dır.
any: boş interface
interface{}'in hiç metodu yoktur, bu yüzden her tip onu karşılar. Go 1.18 bir takma ad olarak any'yi ekledi; ikisi aynıdır.
Bir any değeri her şeyi tutabilir, ama somut tipi bir type assertion ya da type switch ile geri alana kadar onunla neredeyse hiçbir şey yapamazsınız. Tip kümesi biliniyorsa gerçek bir interface'i ya da generics'i tercih edin. any, biçimi bilinmeyen çözülmüş JSON gibi gerçekten dinamik veriye ve yazdırmaya uyar.
Bir interface değeri neler içerir
Bir interface değeri bir çifttir: dinamik bir tip ve dinamik bir değer. var s Shape = Rect{3, 4}, Rect tipini ve değerin bir kopyasını saklar. s.Area()'yı çağırmak Rect'in metodunu çalışma zamanında bulur.
Bir interface yalnızca iki parçası da boş olduğunda nil'dir. Bu kural Go'daki en kafa karıştırıcı hataya yol açar.
Nil interface tuzağı
Bir interface'te saklanan nil bir pointer, nil olmayan bir interface oluşturur.
Çıktı:
false
*main.MyError true
true
validate(true), *MyError tipini ve nil değerini tutan bir error interface'i döndürür. Interface'in bir tipi var, bu yüzden nil'e eşit değildir ve çağıranın if err != nil dalı çalışır. Orada err.Error()'ı çağırmak da nil alıcının alan erişiminde panic'e yol açardı.
Çözüm basit: değişkeni somut pointer tipi olarak değil error olarak tanımlayın ya da başarı yolunda düz bir nil döndürün. Sonucu error olan bir fonksiyondan asla somut bir hata pointer tipi döndürmeyin. Aynı tuzak yalnızca hatalar için değil, her interface için geçerlidir.
Bir tipin bir interface'i gerçekleştirdiğini kontrol etmek
Gerçekleştirme, bir değerin bir interface'e atandığı yerde kontrol edilir. Henüz hiçbir kod bunu yapmıyorsa bir metot imzasındaki hata fark edilmez. Paket seviyesinde boş bir atama kontrolü açık hale getirir:
var _ io.Writer = (*LogWriter)(nil)
var _ fmt.Stringer = Temp(0)
Bunun çalışma zamanında hiçbir maliyeti yoktur. *LogWriter'da Write(p []byte) (int, error) yerine Write(p []byte) error varsa derleme başarısız olur:
cannot use (*LogWriter)(nil) (value of type *LogWriter) as io.Writer value in variable declaration: *LogWriter does not implement io.Writer (wrong type for method Write)
have Write([]byte) error
want Write([]byte) (int, error)
Pointer alıcılar ve interface'ler
Bir metodun pointer alıcısı varsa o metoda yalnızca pointer tipi sahiptir. *Counter bir interface'i onun üzerinden karşılar; Counter karşılamaz. Derleyici Counter does not implement Incrementer (method Inc has pointer receiver) der. Interface'te &Counter{} saklayın. Metot kümelerini metotlar sayfası açıklıyor.
Interface al, struct döndür
Yaygın bir Go ilkesi: fonksiyonlar interface parametreleri almalı ve somut tipler döndürmelidir.
- Bir interface almak, çağıranların test sahteleri dahil uyan her şeyi aktarmasına izin verir. Veri okuyan bir fonksiyon bir
*os.Filedeğil, birio.Readeralmalıdır. - Somut bir tip döndürmek, çağıranların tüm metotlarını ve alanlarını kullanmasına izin verir ve nil interface tuzağından kaçınır.
os.Open,io.Readerdeğil*os.Filedöndürür.
İlgili bir alışkanlık: interface'leri gerçekleştirildikleri yerde değil, kullanıldıkları yerde tanımlayın. Servisinizin bir kullanıcıyı Get(id) ile getirebilen bir şeye ihtiyacı varsa servis paketinizde tek metotlu bir interface tanımlayın ve veritabanı paketinin yalnızca struct'ını dışa açmasına izin verin.
Interface değerlerini karşılaştırmak
Dinamik tipleri aynı ve dinamik değerleri eşit olan iki interface değeri eşittir. Dinamik tip karşılaştırılabilir değilse (bir slice, bir map), == derlenir ama çalışma zamanında panic olur: runtime error: comparing uncomparable type []int.
Sık yapılan hatalar
- Tipli nil bir pointer'ı interface olarak döndürmek. Düz
nildöndürün. - Erken interface. Önce somut tipi yazın. İkinci bir gerçekleştirme ya da bir test ihtiyaç duyduğunda bir interface ekleyin.
- Interface'e pointer.
*io.Readerneredeyse hiçbir zaman doğru değildir. Bir interface'e pointer koyduğunuzda o zaten bir pointer tutar. - Büyük interface'ler. On metotlu interface'leri gerçekleştirmek de sahtesini yapmak da zordur. Bölün.
Sıkça Sorulan Sorular
Go'da bir interface nasıl gerçekleştirilir?
Interface'in listelediği metotları aynı ad ve imzalarla tipiniz üzerinde tanımlayın. implements anahtar kelimesi yoktur. *File'ın Read(p []byte) (int, error) metodu varsa otomatik olarak bir io.Reader'dır. Derleyici bunu, değeri interface tipine atadığınız her yerde kontrol eder.
Go'da boş interface ya da any nedir?
interface{}'in hiç metodu yoktur, bu yüzden her tip onu karşılar. Go 1.18'den beri any, interface{} için yerleşik bir takma addır. any tipinde bir değer her şeyi tutabilir, ama somut bir tipi geri almak için bir type assertion ya da type switch gerekir.
Nil bir pointer atadığım halde Go interface'im neden nil değil?
Bir interface değeri bir tip ve bir değer tutar. Nil bir *MyError'ı bir error'a atamak, tipi *MyError ve değeri nil olan bir interface verir ve o interface nil'e eşit değildir. Hata olmadığında tipli nil bir pointer yerine düz bir nil döndürün.
Bir tipin bir interface'i gerçekleştirdiği derleme zamanında nasıl kontrol edilir?
Paket seviyesinde boş bir atama ekleyin: var _ io.Reader = (*MyReader)(nil). *MyReader'da bir metot eksikse derleme, eksik metodu adlandıran bir mesajla başarısız olur.