pointers Go، X لا يطبق Y(... يحتوي الأسلوب على مستقبل المؤشر)




methods interface (3)

هناك بالفعل العديد من الأسئلة والأجوبة على هذا " X لا يطبق Y (... يحتوي الأسلوب على جهاز استقبال المؤشر) " ، ولكن بالنسبة لي ، يبدو أنها تتحدث عن أشياء مختلفة ، ولا تنطبق على حالتي الخاصة.

لذا ، بدلاً من جعل السؤال محددًا للغاية ، فأنا جعله واسعًا ومجرّدًا - يبدو أن هناك العديد من الحالات المختلفة التي يمكن أن تحدث هذا الخطأ ، هل يمكن لشخص ما تلخيصه من فضلك؟

أي ، كيف تتجنب المشكلة ، وإذا حدثت ، ما هي الاحتمالات؟ شكرًا.


حالة أخرى عندما رأيت هذا النوع من الأشياء يحدث إذا كنت أرغب في إنشاء واجهة حيث ستقوم بعض الطرق بتعديل قيمة داخلية بينما لن يقوم الآخرون بذلك.

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}

يمكن أن يكون شيئًا ما يقوم بتنفيذ هذه الواجهة كما يلي:

type MyTypeA struct {
   a int
}

func (m MyTypeA) GetVal() int {
   return a
}

func (m *MyTypeA) SetVal(newVal int) int {
    int oldVal = m.a
    m.a = newVal
    return oldVal
}

لذلك من المحتمل أن يكون نوع التطبيق بعض الأساليب التي هي مستقبلات المؤشر وبعضها ليس كذلك ، وبما أن لدي مجموعة متنوعة من هذه الأشياء المتنوعة التي هي GetterSetters أود التحقق من الاختبارات التي يقوم بها كل ما هو متوقع.

إذا كنت سأفعل شيئًا كهذا:

myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

ثم لن أحصل على "X" لا يطبق Y (Z لديه مؤشر المتلقي) "خطأ (لأنه هو خطأ وقت الترجمة) ولكن سيكون لي يوم سيء يطارد بالضبط لماذا فشل الاختبار الخاص بي .. .

بدلاً من ذلك ، يجب أن أتأكد من أنني أقوم بإجراء عملية التحقق من النوع باستخدام مؤشر ، مثل:

var f interface{} = new(&MyTypeA)
 ...

أو:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...

ثم كل شيء سعيد مع الاختبارات!

لكن انتظر! في رمز بلدي ، ربما لدي طرق تقبل GetterSetter في مكان ما:

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}

إذا قمت باستدعاء هذه الطرق من داخل طريقة أخرى ، فسيؤدي ذلك إلى حدوث الخطأ:

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

سوف يعمل أي من المكالمات التالية:

func (m *MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}

يظهر خطأ وقت التحويل عند محاولة تعيين أو تمرير (أو تحويل) نوع محدد إلى نوع واجهة ؛ ولا يقوم النوع نفسه بتطبيق الواجهة ، فقط مؤشر إلى النوع .

دعونا نرى مثال على ذلك:

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }

يحتوي نوع واجهة Stringer على طريقة واحدة فقط: String() . أي قيمة يتم تخزينها في قيمة واجهة يجب أن يكون لدى Stringer هذا الأسلوب. قمنا أيضًا بإنشاء MyType ، وقمنا بإنشاء أسلوب MyType.String() مع مستقبل المؤشر . هذا يعني أن أسلوب String() موجود في مجموعة أساليب *MyType ، ولكن ليس في MyType .

عندما نحاول تعيين قيمة MyType لمتغير من نوع Stringer ، نحصل على الخطأ المعني:

m := MyType{value: "something"}

var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
      //   MyType does not implement Stringer (String method has pointer receiver)

ولكن كل شيء على ما يرام إذا حاولنا تعيين قيمة من النوع *MyType to Stringer :

s = &m
fmt.Println(s)

ونحصل على النتيجة المتوقعة (جرب على Go Playground ):

something

وبالتالي فإن متطلبات الحصول على هذا الخطأ وقت التجميع:

  • قيمة النوع الخرساني غير المؤشر الذي يتم تعيينه (أو تمريره أو تحويله)
  • نوع الواجهة التي يتم تعيينها (أو تمريرها إلى ، أو تحويلها إلى)
  • نوع الخرسانة لديه الطريقة المطلوبة للواجهة ، ولكن مع جهاز استقبال المؤشر

إمكانيات لحل المشكلة:

  • يجب استخدام مؤشر إلى القيمة ، حيث ستتضمن مجموعة الطريقة الخاصة به الأسلوب مع جهاز استقبال المؤشر
  • أو يجب تغيير نوع المستقبِل إلى غير مؤشر ، لذا فإن مجموعة طرق نوع الخرسانة غير المؤشر سيحتوي أيضًا على الطريقة (وبالتالي تلبية السطح البيني). هذا قد يكون أو لا يكون قابلا للتطبيق ، كما لو أن الطريقة يجب أن تقوم بتعديل القيمة ، فإن مستقبل غير المؤشر ليس خيارا.

الهياكل والتضمين

عند استخدام التركيبات والتضمين ، غالبًا لا تقوم "أنت" بتنفيذ واجهة (توفير تطبيق أسلوب) ، ولكن نوع قمت بتضمينها في struct الخاصة بك. كما هو الحال في هذا المثال:

type MyType2 struct {
    MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: m}

var s Stringer
s = m2 // Compile-time error again

مرة أخرى ، خطأ وقت التحويل ، لأن مجموعة الطريقة MyType2 لا تحتوي على طريقة String() في MyType المضمنة ، فقط مجموعة أساليب *MyType2 ، لذا فإن الأعمال التالية (جربها على Go Playground ):

var s Stringer
s = &m2

يمكننا أيضًا جعلها تعمل ، إذا قمنا بتضمين *MyType واستخدام MyType2 غير مؤشر (جربها على Go Playground ):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = m2

وأيضًا ، بغض النظر عن تضميننا (إما MyType أو *MyType ) ، إذا استخدمنا مؤشر *MyType2 ، *MyType2 ذلك دائمًا ( *MyType2 على Go Playground ):

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = &m2

القسم ذو الصلة من المواصفات (من قسم أنواع البنية ):

وبالنظر إلى نوع البنية S والنوع المسمى T ، فإن الطرق المروجة يتم تضمينها في مجموعة طرق البنية كما يلي:

  • إذا احتوى S على حقل مجهول T ، فإن طريقتين S و *S تشملان أساليب مروجة مع جهاز الاستقبال T تتضمن مجموعة أساليب *S أيضًا طرقًا ترويجية مع جهاز الاستقبال *T
  • إذا احتوى S على حقل مجهول *T ، فإن طريقتين S و *S تشملان أساليب تم ترويجها مع جهاز الاستقبال T أو *T

لذلك بعبارة أخرى: إذا قمنا بتضمين نوع غير مؤشّر ، فإن مجموعة الأسلوب الخاصة بالملزم غير المؤشر لا تحصل إلا على الطرق مع المستقبلات غير المؤشر (من النوع المضمن).

إذا قمنا بتضمين نوع مؤشر ، فإن مجموعة طريقة من العارض غير المؤشر يحصل على طرق مع كل من مستقبلات المؤشر وغير المؤشر (من النوع المضمن).

إذا استخدمنا قيمة مؤشر إلى الإدماج ، بغض النظر عن ما إذا كان النوع المضمن مؤشر أو لا ، فإن مجموعة طريقة المؤشر إلى المُضمِّن تحصل دائمًا على طرق مع كل من مستقبلات المؤشر وغير المؤشر (من النوع المضمن).

ملحوظة:

هناك حالة مشابهة جدًا ، وهي عندما يكون لديك قيمة واجهة تعمل على التفاف قيمة MyType ، وتحاول كتابة تأكيد قيمة واجهة أخرى منه ، Stringer . في هذه الحالة ، لن يتم التوكيد للأسباب المذكورة أعلاه ، ولكننا نحصل على خطأ في وقت التشغيل مختلف قليلاً:

m := MyType{value: "something"}

var i interface{} = m
fmt.Println(i.(Stringer))

ذعر وقت التشغيل (جرب على Go Playground ):

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String

عند محاولة التحويل بدلاً من كتابة التأكيد ، نحصل على خطأ وقت التحويل الذي نتحدث عنه:

m := MyType{value: "something"}

fmt.Println(Stringer(m))

لإبقائها قصيرة ، دعنا نقول أن لديك هذا الرمز ولديك واجهة Loader و WebLoader الذي ينفذ هذه الواجهة.

package main

import "fmt"

// Loader defines a content loader
type Loader interface {
    Load(src string) string
}

// WebLoader is a web content loader
type WebLoader struct{}

// Load loads the content of a page
func (w *WebLoader) Load(src string) string {
    return fmt.Sprintf("I loaded this page %s", src)
}

func main() {
    webLoader := WebLoader{}
    loadContent(webLoader)
}

func loadContent(loader Loader) {
    loader.Load("google.com")
}

لذا سيعطيك هذا الرمز خطأ وقت التجميع هذا

./main.go:20:13: لا يمكن استخدام webLoader (اكتب WebLoader) كنوع Loader في الوسيطة لتحميل loadContent: WebLoader لا يقوم بتطبيق Loader (يحتوي أسلوب Load على مؤشر المؤشر)

إذن ما عليك القيام به هو تغيير webLoader := WebLoader{} إلى ما يلي:

webLoader := &WebLoader{} 

لذلك لماذا سوف إصلاح لأنك تحدد هذه الوظيفة func (w *WebLoader) Load لقبول جهاز استقبال المؤشر. لمزيد من التوضيح ، يرجى قراءة إجاباتicza وkarora





interface