Arayüz Alanlarına Git


106

Go'da arayüzlerin verilerden çok işlevselliği tanımladığı gerçeğine aşinayım. Bir arabirime bir dizi yöntem koyarsınız, ancak bu arabirimi uygulayan herhangi bir şey için gerekli olabilecek alanları belirleyemezsiniz.

Örneğin:

// 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
}

Şimdi arayüzü ve uygulamalarını kullanabiliriz:

// 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
*/

Şimdi, yapamayacağın şey şöyle bir şey:

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)
}

Bununla birlikte, arayüzler ve gömülü yapılarla uğraştıktan sonra, bir modadan sonra bunu yapmanın bir yolunu keşfettim:

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
}

Gömülü yapı nedeniyle Bob, Person'un sahip olduğu her şeye sahiptir. Ayrıca PersonProvider arabirimini de uygular, böylece Bob'u bu arabirimi kullanmak için tasarlanmış işlevlere aktarabiliriz.

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)
}

İşte yukarıdaki kodu gösteren bir Go Playground .

Bu yöntemi kullanarak davranıştan çok veriyi tanımlayan ve herhangi bir yapı tarafından sadece bu veriyi gömerek uygulanabilen bir arayüz oluşturabilirim. Bu gömülü verilerle açıkça etkileşime giren ve dış yapının doğasının farkında olmayan işlevler tanımlayabilirsiniz. Ve her şey derleme zamanında kontrol edilir! (Eğer pisliği, görebildiğim ki tek yolu, arayüz gömme olacağını PersonProvideriçinde Bobyerine bir betondan daha, Person. O derlemek ve zamanında başarısız olur.)

Şimdi, sorum şu: bu güzel bir numara mı yoksa farklı bir şekilde mi yapmalıyım?


4
"Davranıştan çok veriyi tanımlayan bir arayüz yapabilirim". Veri döndüren bir davranışınız olduğunu iddia ediyorum.
jmaloney

Bir cevap yazacağım; Bence ihtiyacın olursa ve sonuçlarını biliyorsan sorun değil, ama sonuçları var ve ben bunu her zaman yapmam.
twotwotwo

@jmaloney Açıkça bakmak istersen haklısın. Ancak genel olarak, gösterdiğim farklı parçalarla, anlambilim "bu işlev, bileşiminde ___ bulunan herhangi bir yapıyı kabul eder" haline gelir. En azından amacım buydu.
Matt Mc

1
Bu "cevap" malzemesi değil. Sorunuza "yapı özelliği golang olarak arabirim" ibaresini vererek geldim. Başka bir yapının özelliği olarak bir arabirim uygulayan bir yapı belirleyerek benzer bir yaklaşım buldum. İşte oyun alanı, play.golang.org/p/KLzREXk9xo Bana fikir verdiğiniz için teşekkürler.
Dale

1
Geriye dönüp baktığımda ve Go'yu 5 yıl kullandıktan sonra, yukarıdakilerin deyimsel Go olmadığı bana açık. Jenerik ilaçlara karşı bir zorlama. Bu tür bir şey yapmak için cazip hissediyorsanız, sisteminizin mimarisini yeniden düşünmenizi tavsiye ederim. Arayüzleri kabul edin ve yapıları iade edin, iletişim kurarak paylaşın ve sevinin.
Matt Mc

Yanıtlar:


55

Kesinlikle güzel bir numara. Ancak, işaretçileri açığa çıkarmak, verilere doğrudan erişim sağlamaya devam eder, bu nedenle size yalnızca gelecekteki değişiklikler için sınırlı ek esneklik kazandırır. Ayrıca, Go kuralları, veri özniteliklerinizin önüne her zaman bir soyutlama koymanızı gerektirmez .

Bunları bir araya getirerek, belirli bir kullanım durumu için bir uç noktaya ya da diğerine yönelirim: ya a) sadece bir genel özellik oluşturun (varsa gömme kullanın) ve etrafta somut türleri iletin ya da b) verileri ifşa etmek gibi görünüyorsa daha sonra sorun yaratırsa, daha sağlam bir soyutlama için alıcı / ayarlayıcıyı ortaya çıkarın.

Bunu öznitelik başına tartacaksınız. Örneğin, bazı veriler uygulamaya özelse veya başka bir nedenle temsilleri değiştirmeyi bekliyorsanız, muhtemelen özniteliği doğrudan ifşa etmek istemezsiniz, oysa diğer veri öznitelikleri, onları herkese açık hale getirmenin net bir kazanç olacağı kadar kararlı olabilir.


Alıcıların ve ayarlayıcıların arkasındaki özellikleri gizlemek, daha sonra geriye doğru uyumlu değişiklikler yapmanız için size ekstra esneklik sağlar. Bir gün Personsadece tek bir "ad" alanını değil, aynı zamanda ilk / orta / son / önek depolamak için değiştirmek istediğinizi varsayalım; Yöntemleriniz varsa Name() stringve daha ince ayrıntılı yeni yöntemler eklerken SetName(string)mevcut Personarabirim kullanıcılarını mutlu edebilirsiniz. Ya da, veritabanı destekli bir nesneyi kaydedilmemiş değişiklikler içerdiğinde "kirli" olarak işaretleyebilmek isteyebilirsiniz; veri güncellemelerinin tümü SetFoo()yöntemlerden geçtiğinde bunu yapabilirsiniz .

Yani: alıcılar / ayarlayıcılar ile uyumlu bir API'yi korurken yapı alanlarını değiştirebilir ve hiç kimse p.Name = "bob"kodunuzu gözden geçirmeden yapamayacağı için özellik get / sets etrafına mantık ekleyebilirsiniz .

Bu esneklik, tür karmaşık olduğunda (ve kod tabanı büyük olduğunda) daha önemlidir. Eğer bir a sahipseniz, PersonCollectiondahili olarak bir sql.Rows, a []*Person, a []uintveritabanı kimlikleri veya her neyse , tarafından destekleniyor olabilir . Doğru arayüzü kullanarak, arayanları önemsemekten kurtarabilirsiniz, bu yöntem io.Readerağ bağlantılarını ve dosyaları benzer hale getirir.

Spesifik bir şey: interfaceGo'da, onu tanımlayan paketi içe aktarmadan uygulayabileceğiniz tuhaf özelliğe sahiptir; bu, döngüsel içe aktarmalardan kaçınmanıza yardımcı olabilir . Arayüzünüz *Personsadece dizeler veya herhangi bir şey yerine a döndürürse , hepsinin tanımlandığı PersonProviderspaketi içe Personaktarması gerekir. Bu iyi veya hatta kaçınılmaz olabilir; sadece bilinmesi gereken bir sonuç.


Ancak yine, Go topluluğunun, türünüzün genel API'sinde veri üyelerini göstermeye karşı güçlü bir kuralı yoktur . Belirli bir durumda API'nizin bir parçası olarak bir özniteliğe genel erişimi kullanmanın, daha sonra bir uygulama değişikliğini muhtemelen karmaşıklaştırabileceği veya önleyebileceği için herhangi bir teşhirden vazgeçirmek yerine, sizin kararınıza bırakılmıştır .

Örneğin, stdlib, http.Serveryapılandırmanızla bir başlatmanıza izin vermek gibi şeyler yapar ve sıfırın bytes.Bufferkullanılabilir olduğunu vaat eder . Kendi işlerinizi böyle yapmakta sorun yok ve aslında, daha somut, veriyi açığa çıkaran versiyonun işe yaraması muhtemel görünüyorsa, işleri öncelikli olarak soyutlamanız gerektiğini düşünmüyorum. Bu sadece değiş tokuşların farkında olmakla ilgili.


Ek bir şey: yerleştirme yaklaşımı biraz daha kalıtıma benzer, değil mi? Gömülü yapının sahip olduğu tüm alanları ve yöntemleri elde edersiniz ve arayüz setlerini yeniden uygulamaya gerek kalmadan herhangi bir üst yapının uygun hale gelmesi için arayüzünü kullanabilirsiniz.
Matt Mc

Evet - diğer dillerdeki sanal kalıtım gibi. Alıcılar ve ayarlayıcılar ya da verilere bir işaretçi (veya minik yapılara salt okunur erişim için üçüncü bir seçenek, yapının bir kopyası) açısından tanımlanmış olsun, bir arabirimi uygulamak için yerleştirmeyi kullanabilirsiniz.
twotwotwo

Söylemeliyim ki, bu bana 1999'a geri dönüşler veriyor ve Java'da klişe alıcılar ve ayarlayıcılar yazmayı öğreniyor.
Tom

Go'nun kendi standart kitaplığının her zaman bunu yapmaması çok kötü. Birim testleri için os.Process'e bazı çağrıları alay etmeye çalışıyorum. Pid üye değişkenine doğrudan erişildiğinden ve Go arayüzleri üye değişkenlerini desteklemediğinden, işlem nesnesini bir arayüze sığdıramıyorum.
Alex Jansen

1
@Tom Bu doğru. Alıcıların / ayarlayıcıların bir işaretçiyi açığa çıkarmaktan daha fazla esneklik sağladığını düşünüyorum , ancak herkesin her şeyi (veya tipik Go stiline uyan) alıcı / ayarlayıcı olması gerektiğini de düşünmüyorum. Daha önce bunu işaret eden birkaç kelimem vardı, ancak daha çok vurgulamak için başını ve sonunu gözden geçirdim.
twotwotwo

2

Doğru anladıysam, bir yapı alanını diğerine doldurmak istiyorsunuz. Bence arayüzleri genişletmek için kullanmamak. Bir sonraki yaklaşımla bunu kolayca yapabilirsiniz.

package main

import (
    "fmt"
)

type Person struct {
    Name        string
    Age         int
    Citizenship string
}

type Bob struct {
    SSN string
    Person
}

func main() {
    bob := &Bob{}

    bob.Name = "Bob"
    bob.Age = 15
    bob.Citizenship = "US"

    bob.SSN = "BobSecret"

    fmt.Printf("%+v", bob)
}

https://play.golang.org/p/aBJ5fq3uXtt

Not Person içinde Bobbildiriminde. Bu, dahil edilen yapı alanının bir Bobmiktar sözdizimsel şeker ile doğrudan yapıda kullanılabilir hale getirilecektir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.