كيفية استخدام الواجهات في Go



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

يحتوي هذا المنشور على توصيات من المؤلف حول الاستخدام الأمثل للواجهات في Go ، استنادًا إلى خبرته في كتابة التعليمات البرمجية.

في أمثلة هذا المنشور ، سنستخدم حزمتين من animal circus . تصف أشياء كثيرة في هذا المنشور العمل مع حدود الكود على الاستخدام المنتظم للحزم.

كيف لا تفعل


ظاهرة شائعة جدًا ألاحظها:

 package animals type Animal interface { Speaks() string } //  Animal type Dog struct{} func (a Dog) Speaks() string { return "woof" } 

 package circus import "animals" func Perform(a animal.Animal) string { return a.Speaks() } 

هذا هو ما يسمى استخدام واجهات نمط جافا. يمكن أن تتميز بالخطوات التالية:

  1. تحديد واجهة.
  2. تحديد نوع واحد يلبي سلوك الواجهة.
  3. تحديد الأساليب التي تلبي تنفيذ الواجهة.

باختصار ، نحن نتعامل مع "أنواع الكتابة التي ترضي الواجهات". هذه الشفرة لها رائحة مميزة خاصة بها ، تقترح الأفكار التالية:

  • نوع واحد فقط يفي بالواجهة ، دون أي نية لتوسيعها أكثر.
  • عادة ما تأخذ الوظائف أنواعًا محددة بدلاً من أنواع الواجهة.

كيف تفعل بدلا من ذلك


تشجع الواجهات في Go على اتباع نهج كسول ، وهو أمر جيد. بدلاً من كتابة أنواع تفي بالواجهات ، يجب عليك كتابة الواجهات التي تلبي المتطلبات العملية الحقيقية.

مما يعني: بدلاً من تعريف Animal في حزمة animals ، حدده في نقطة الاستخدام ، أي حزمة circus * .

 package animals type Dog struct{} func (a Dog) Speaks() string { return "woof" } 

 package circus type Speaker interface { Speaks() string } func Perform(a Speaker) string { return a.Speaks() } 

الطريقة الأكثر طبيعية للقيام بذلك هي كما يلي:

  1. تحديد الأنواع
  2. حدد الواجهة عند نقطة الاستخدام.

يقلل هذا النهج من الاعتماد على مكونات حزمة animals . إن تقليل التبعيات هو الطريقة الصحيحة لإنشاء برنامج يتحمل الأخطاء.

قانون السرير


هناك مبدأ جيد واحد لكتابة برامج جيدة. هذا هو قانون Postel ، والذي غالبًا ما يتم صياغته على النحو التالي:
"كن متحفظًا بشأن ما تشير إليه وليبراليًا بشأن ما تقبله"
من حيث Go ، فإن القانون هو:

"قبول الواجهات وهياكل العودة"

بشكل عام ، هذه قاعدة جيدة جدًا لتصميم أشياء مستقرة تتسامح مع الأخطاء * . الوحدة الرئيسية للرمز في Go هي وظيفة. عند تصميم الوظائف والأساليب ، من المفيد الالتزام بالنمط التالي:

 func funcName(a INTERFACETYPE) CONCRETETYPE 

هنا نقبل كل ما ينفذ واجهة يمكن أن تكون أي شيء ، بما في ذلك واجهة فارغة. تم اقتراح قيمة من نوع معين. بالطبع ، تحديد ما يمكن أن يكون منطقيًا. كما يقول المثل Go واحد:

"الواجهة الفارغة لا تقول شيئًا" روب بايك

لذلك ، من المستحسن للغاية منع الوظائف من قبول interface{} .

مثال على التطبيق: التقليد


من الأمثلة البارزة على فوائد تطبيق قانون Postel حالات الاختبار. لنفترض أن لديك وظيفة تبدو كما يلي:

 func Takes(db Database) error 

إذا كانت Database هي واجهة ، فعندئذٍ يمكنك في رمز الاختبار ببساطة تقديم محاكاة لتطبيق Database دون الحاجة إلى تمرير كائن DB حقيقي.

عندما يكون تعريف الواجهة مقدما مقبولا


لإخبارك بالحقيقة ، تعد البرمجة طريقة مجانية للتعبير عن الأفكار. لا توجد قواعد لا تتزعزع. بالطبع ، يمكنك دائمًا تحديد الواجهات مقدمًا ، دون خوف من أن يتم القبض عليك من قبل الشرطة. في سياق العديد من الحزم ، إذا كنت تعرف وظائفك وتنوي قبول واجهة معينة داخل الحزمة ، فافعل ذلك.

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

  • واجهات مختومة
  • أنواع البيانات المجردة
  • الواجهات العودية

بعد ذلك ، نعتبر كل واحد منهم بإيجاز.

واجهات مختومة


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

إذا قمت بتعريف شيء مثل هذا:

 type Fooer interface { Foo() sealed() } 

فقط الحزمة المحددة بواسطة Fooer يمكنها استخدامها وإنشاء أي شيء له قيمة. يسمح لك هذا بإنشاء عوامل تشغيل تبديل القوة الغاشمة للأنواع.

كما تسمح الواجهة المختومة لأدوات التحليل بالتقاط أي تطابقات في نمط عدم التصادم بسهولة. تهدف حزمة sumtpes الموجودة في BurntSushi إلى حل هذه المشكلة.

أنواع البيانات المجردة


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

من الأمثلة الجيدة على ذلك حزمة sort ، والتي تعد جزءًا من المكتبة القياسية. يحدد مجموعة قابلة للفرز على النحو التالي

 type Interface interface { // Len —    . Len() int // Less      //   i     j. Less(i, j int) bool // Swap     i  j. Swap(i, j int) } 

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

ومع ذلك ، أجد أن هذا شكل أنيق للغاية من الأدوية الجنسية في Go. يجب تشجيع استخدامه في كثير من الأحيان.

ستتطلب خيارات التصميم البديلة والأنيقة في نفس الوقت أنواع طلبات أعلى. في هذا المنشور لن نعتبرها.

الواجهات العودية


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

 type Fooer interface { Foo() Fooer } 

من الواضح أن نمط الواجهة العودية سيتطلب تعريفه مسبقًا. توصية تعريف واجهة نقطة الاستخدام غير قابلة للتطبيق هنا.

هذا النمط مفيد لإنشاء سياقات مع عمل لاحق فيها. عادة ما يشتمل الرمز الذي تم تحميله بواسطة السياق على نفسه داخل الحزمة بتصدير السياقات فقط (ala tensor package) ، وبالتالي ، في الممارسة العملية ، لا أواجه هذه الحالة في كثير من الأحيان. يمكنني أن أخبرك بشيء آخر عن الأنماط السياقية ، لكن اتركه لمنشور آخر.

الخلاصة


على الرغم من حقيقة أن أحد عناوين المنشور يقرأ "كيف لا تفعل ذلك" ، فأنا لا أحاول بأي حال من الأحوال حظر أي شيء. بدلا من ذلك ، أريد أن أجعل القراء يفكرون في كثير من الأحيان حول ظروف الحدود ، حيث أنه في مثل هذه الحالات تنشأ حالات طوارئ مختلفة.

أجد مبدأ إعلان نقطة الاستخدام مفيدا للغاية. نتيجة لتطبيقه في الممارسة العملية ، لا أواجه مشاكل تنشأ إذا أهملته.

ومع ذلك ، فإنني أحيانًا أكتب واجهات على غرار Java. عادةً ما يحدث هذا إذا كتبت قبل فترة وجيزة الكثير من التعليمات البرمجية بلغة Java أو Python. تكون الرغبة في الإفراط في التعقيد و "تمثيل كل شيء في شكل فئات" قوية جدًا في بعض الأحيان ، خاصة إذا كتبت رمز Go بعد كتابة الكثير من التعليمات البرمجية الموجهة للكائنات.

وبالتالي ، فإن هذا المنشور أيضًا بمثابة تذكير لنفسه حول ما يبدو عليه مسار كتابة التعليمات البرمجية لن يسبب صداعًا في وقت لاحق. في انتظار تعليقاتك!

الصورة

Source: https://habr.com/ru/post/ar411029/


All Articles