"<Tür>, arayüzün göstergesidir, arayüze değil" karışıklığı


117

Sevgili geliştiriciler,

Bana biraz tuhaf görünen bu problemim var. Şu kod pasajına bir göz atın:

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID     
    RemoveFilter(i uuid.UUID)                   
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
}

func (fp *FilterMap) RemoveFilter(i uuid.UUID) {
    // Some code
}

func (fp *FilterMap) GetFilterByID(i uuid.UUID) *FilterInterface {
    // Some code
}

Başka bir pakette aşağıdaki koda sahibim:

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter) // <--- Exception is raised here
}

Çalışma zamanı belirtilen satırı kabul etmiyor çünkü

"fieldint.AddFilter değişkeninde * coreinterfaces.FilterInterface türü olarak fieldfilter (type * coreinterfaces.FieldFilter) kullanılamaz: * coreinterfaces.FilterInterface arabirime değil arabirime işaretleyicidir"

Ancak, kodu şu şekilde değiştirirken:

func DoBid() error {
    bs := string(b)
    var ifilterfield coreinterfaces.FilterInterface
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    ifilterfield = fieldfilter
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(&ifilterfield)
}

Her şey yolunda ve uygulamada hata ayıklarken gerçekten içeriyor gibi görünüyor

Bu konuda biraz kafam karıştı. Aynı sorunu tartışan diğer blog gönderilerine ve yığın taşma konularına bakarken (örneğin - Bu veya Bu ) bu istisnayı ortaya çıkaran ilk kod parçası çalışmalıdır, çünkü hem alan filtresi hem de alan haritası değeri yerine arayüzlere işaretçiler olarak başlatılır. arayüzler. Bir FieldInterface bildirmemem ve bu arabirim için uygulamayı atamamam için değiştirmem gereken burada gerçekte olanlara kafamı veremedim. Bunu yapmanın zarif bir yolu olmalı.


Değiştirirken * FilterInterfaceiçin FilterInterfacehat _ = filtermap.AddFilter(fieldfilter): Bu yükseltir coreinterfaces.FieldFilter coreinterfaces.FilterInterface uygulamaz (süzme yöntemi işaretçi alıcısı vardır): filtermap.AddFilter argüman tip coreinterfaces.FilterInterface olarak (tip coreinterfaces.FieldFilter) fieldfilter kullanamaz değiştirirken Ancak satır _ = filtermap.AddFilter(&fieldfilter)buna çalışır. Burada ne oluyor? Neden?
0rka

2
Çünkü arayüzü uygulayan yöntemlerin işaretçi alıcıları vardır. Bir değer iletmek, arabirimi uygulamaz; bir işaretçi iletir, çünkü yöntemler daha sonra uygulanır. Genel olarak, arayüzlerle uğraşırken, bir yapıya, bir arayüz bekleyen bir işleve bir işaretçi iletirsiniz. Herhangi bir senaryoda, neredeyse hiçbir zaman bir arabirime işaretçi istemezsiniz.
Adrian

1
Amacınızı anlıyorum, ancak parametre değerini * FilterInterfacebu arabirimi uygulayan bir yapıya değiştirerek, bu arabirimleri işlevlere geçirme fikrini kırar. Başarmak istediğim şey, geçmekte olduğum yapıya değil, kullanmak istediğim arayüzü uygulayan herhangi bir yapıya bağlı olmaktır. Benim için daha verimli veya standartlara uygun olduğunu düşündüğünüz herhangi bir kod değişikliği var mı? Bazı kod inceleme hizmetlerini kullanmaktan memnun olacağım :)
0rka

2
İşleviniz bir arabirim bağımsız değişkenini kabul etmelidir (arabirime işaretçi değil). Arayan, arayüzü uygulayan bir yapıya bir işaretçi iletmelidir. Bu, "arabirimleri işlevlere geçirme fikrini bozmaz" - işlev hala bir arabirim alır, arabirimi uygulayan bir somutlaştırmaya geçersiniz.
Adrian

Yanıtlar:


158

Yani burada iki kavramı karıştırıyorsunuz. Bir yapıya işaretçi ile bir arabirime işaretçi aynı değildir. Bir arabirim ya bir yapıyı doğrudan ya da bir yapıya bir gösterici depolayabilir . İkinci durumda, arayüz için bir işaretçi değil , sadece arayüzü doğrudan kullanırsınız. Örneğin:

type Fooer interface {
    Dummy()
}

type Foo struct{}

func (f Foo) Dummy() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

Çıktı:

[main.Foo] {}
[*main.Foo] &{}

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

Her iki durumda da, içindeki fdeğişken DoFoosadece bir arabirimdir, bir arabirime işaretçi değildir . Ancak, depolama f2sırasında arayüz bir yapıya bir işaretçi tutarFoo .

Arayüzlere işaretçiler neredeyse hiçbir zaman işe yaramaz. Aslında, Go çalışma zamanı, kullanımlarını caydırmak için birkaç sürümde özel olarak değiştirildi ve artık otomatik olarak başvurulmayan arabirim işaretçilerine (yapı işaretçileri için yaptığı gibi) yeniden referans verilmeyecek şekilde değiştirildi. Çoğu durumda, bir arayüze işaretçi, arayüzlerin nasıl çalışması gerektiğine dair yanlış anlaşılmayı yansıtır.

Ancak, arayüzlerde bir sınırlama vardır. Bir yapıyı doğrudan bir arabirime aktarırsanız , arabirimi yerine getirmek için yalnızca bu türdeki değer yöntemleri (yani func (f Foo) Dummy(), değil func (f *Foo) Dummy()) kullanılabilir. Bunun nedeni, arabirimde orijinal yapının bir kopyasını depoluyor olmanızdır, bu nedenle işaretçi yöntemlerinin beklenmedik etkileri olur (yani, orijinal yapıyı değiştiremez). Bu nedenle, varsayılan kural, yapmamak için zorlayıcı bir neden olmadıkça , arayüzlerdeki yapılara işaretçileri depolamaktır .

Özellikle kodunuzla, AddFilter işlev imzasını şu şekilde değiştirirseniz:

func (fp *FilterMap) AddFilter(f FilterInterface) uuid.UUID

Ve GetFilterByID imzası:

func (fp *FilterMap) GetFilterByID(i uuid.UUID) FilterInterface

Kodunuz beklendiği gibi çalışacaktır. fieldfiltertiptedir *FieldFilterfullfills, FilterInterfaceböylece arayüz türü ve AddFilteronu kabul edecektir.

Yöntemlerin, türlerin ve arabirimlerin Go'da nasıl çalıştığını ve birbirleriyle nasıl bütünleştiğini anlamak için işte birkaç iyi referans:


"Bunun nedeni, arabirimde orijinal yapının bir kopyasını saklamanızdır, bu nedenle işaretçi yöntemlerinin beklenmedik etkileri olacaktır (yani, orijinal yapıyı değiştiremezler)" - bu, sınırlamanın nedeni olarak mantıklı değil. Sonuçta, tek kopya baştan beri arayüzde saklanmış olabilir.
WPWoodJr

Cevabınız hiçbir anlam ifade etmiyor. Arayüzde depolanan somut türün, orada depolanan şeyi değiştirdiğinizde değişmediğini varsayıyorsunuz, bu durum böyle değil ve farklı bir bellek düzenine sahip bir şey depoluyorsanız bu açık olmalıdır. İşaretçi yorumumla ilgili anlamadığınız şey, somut bir türdeki bir işaretçi alıcı yönteminin çağrıldığı alıcıyı her zaman değiştirebileceğidir. Bir değer işaretçi alıcıları Yani o zaman bir referans alamayan bir ara yüz kuvvetlerinde bir kopyasını muhafaza edemez orijinali değiştirin dönemi.
Kaedys

5
GetFilterByID(i uuid.UUID) *FilterInterface

Bu hatayı aldığımda, bunun nedeni genellikle bir arabirim yerine bir arabirime bir işaretçi belirttiğim içindir (bu aslında arabirimi yerine getiren yapıma bir işaretçi olacaktır).

* Arayüz {...} için geçerli bir kullanım var, ancak daha yaygın olarak, "yazdığım kodda bir işaretçi olan bir arayüz" yerine "bu bir işaretçi" diye düşünüyorum

Bunu oraya atmak, çünkü kabul edilen cevap ayrıntılı olsa da sorun gidermeme yardımcı olmadı.

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.