عدم السماح باستيراد الدورة في Go: كيفية كسر الحلقة المفرغة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Import cycle not allowed: عندما تضع Go حاجزًا في مسار الكود

\n\n

كل مطور Go يواجه عاجلاً أم آجلاً هذه العبارة. يقطع المترجم عملية البناء فجأة، مصدرًا رسالة جافة: import cycle not allowed. يبدو أن البرنامج منطقي والحزم مترابطة "كما ينبغي". لكن Go ترى غير ذلك.

\n\n

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

\n\n

لماذا تمنع Go الاستيراد الدائري؟

\n\n

على عكس اللغات ذات التحميل الديناميكي (Python, JavaScript)، يتم تجميع Go بشكل ثابت. يبني المترجم رسمًا بيانيًا لتبعيات الحزم مرة واحدة. إذا كانت الحزمة A تستورد الحزمة B، و B تستورد A - يحدث تكرار دائري. لا تستطيع Go حل هذا الترتيب، لذا توقف عملية التجميع.

\n\n

مثال من الواقع: حزمة users تتعامل مع المستخدمين، حزمة orders تتعامل مع الطلبات. في orders نحتاج معلومات عن المستخدم، وفي users نحتاج قائمة الطلبات. النتيجة: import cycle not allowed.

\n\n

سيناريو نموذجي: حزمتان "تتآلفان" بشكل وثيق جدًا

\n\n

لننظر إلى موقف كلاسيكي. لدينا حزمتان: users و notifier.

\n\n

حزمة users (ملف users.go):

\n
package users\n\nimport "notifier"\n\ntype User struct {\n    Name  string\n    Email string\n}\n\nfunc (u *User) SendWelcome() {\n    notifier.Notify(u.Email, "Welcome!")\n}\n
\n\n

حزمة notifier (ملف notifier.go):

\n
package notifier\n\nimport "users"\n\nfunc Notify(email, msg string) {\n    // إرسال إشعار\n}\n\nfunc GetUserEmail(user *users.User) string {\n    return user.Email\n}\n
\n\n

سيصدر المترجم: import cycle not allowed: users -> notifier -> users. كلتا الحزمتين تحاولان استيراد بعضهما البعض. ما الحل؟

\n\n

الطريقة 1: الواجهات - "فرق تسد"

\n\n

الطريقة الأكثر أناقة هي نقل التبعية إلى مستوى الواجهة. Go تحب العقود الصريحة.

\n\n

ننشئ حزمة contracts، حيث نحدد واجهة Notifier:

\n
// contracts/contracts.go\npackage contracts\n\ntype Notifier interface {\n    Notify(email, msg string)\n}\n
\n\n

الآن users لا تعتمد على notifier بشكل مباشر:

\n
// users/users.go\npackage users\n\nimport "contracts"\n\ntype User struct {\n    Name  string\n    Email string\n}\n\nfunc (u *User) SendWelcome(n contracts.Notifier) {\n    n.Notify(u.Email, "Welcome!")\n}\n
\n\n

و notifier تنفذ الواجهة:

\n
// notifier/notifier.go\npackage notifier\n\ntype EmailNotifier struct{}\n\nfunc (e *EmailNotifier) Notify(email, msg string) {\n    // منطق الإرسال\n}\n
\n\n

المزايا:

\n
    \n
  • غياب تام للاستيراد الدائري.
  • \n
  • سهولة الاختبار: يمكن استبدال التنفيذ.
  • \n
  • اقتران ضعيف (loose coupling).
  • \n
\n

العيوب:

\n
    \n
  • يتطلب حزمة إضافية للواجهات.
  • \n
  • أحيانًا لا تكون الواجهات ضرورية للمشاريع البسيطة.
  • \n
\n\n

الطريقة 2: الحزمة الوسيطة (Mediator)

\n\n

إذا بدت الواجهات زائدة عن الحاجة، يمكن إنشاء حزمة ثالثة تكون "جسرًا" بين الحزمتين المتعارضتين.

\n\n

ننشئ حزمة bridge:

\n
// bridge/bridge.go\npackage bridge\n\nimport (\n    "notifier"\n    "users"\n)\n\nfunc SendWelcomeToUser(user *users.User) {\n                

المدونات

توصيات الكتب