الاعتمادية الدائرية.
الاعتمادية الدائرية نمط مضاد تعتمد فيه وحدتان أو أكثر إحداهما على الأخرى، فلا يمكن بناء أي منها أو اختبارها أو إعادة استخدامها منفردة.
في هندسة البرمجيات، الاعتمادية الدائرية (أو الاعتمادية الحلقية cyclic dependency) هي علاقة بين موديولين أو أكثر يعتمد كل منهما على الآخر بشكل مباشر أو غير مباشر لكي يعمل بشكل صحيح. وتعرف مثل هذه الموديولات أيضاً بأنها ذات استدعاء ذاتي متبادل (mutually recursive).
نظرة عامة
الاعتمادات الدائرية طبيعية في العديد من نماذج النطاقات (domain models) حيث تعتمد كائنات معينة من نفس النطاق على بعضها البعض. ومع ذلك، في تصميم البرمجيات، تعتبر الاعتمادات الدائرية بين موديولات البرمجيات الأكبر حجماً نمطاً مضاداً بسبب آثارها السلبية. وعلى الرغم من ذلك، فقد وُجد أن مثل هذه الاعتمادات الدائرية (أو الحلقية) واسعة الانتشار بين ملفات المصدر للبرمجيات في العالم الحقيقي. ومع ذلك، فإن الموديولات ذات الاستدعاء الذاتي المتبادل شائعة إلى حد ما في البرمجة الوظيفية، حيث غالباً ما يتم تشجيع التعريفات الاستقرائية والتكرارية.
المشكلات
يمكن أن تسبب الاعتمادات الدائرية العديد من الآثار غير المرغوب فيها في البرامج الحاسوبية. والأكثر إشكالية من وجهة نظر تصميم البرمجيات هو الترابط الوثيق coupling للموديولات المعتمدة متبادلاً مما يقلل أو يجعل من المستحيل إعادة استخدام موديول واحد بشكل منفصل.
يمكن أن تسبب الاعتمادات الدائرية تأثير الدومينو (domino effect) عندما ينتشر تغيير محلي صغير في موديول واحد إلى موديولات أخرى ويكون له آثار عالمية غير مرغوب فيها (أخطاء في البرنامج، أخطاء تجميع). ويمكن أن تؤدي الاعتمادات الدائرية أيضاً إلى استدعاءات ذاتية لانهائية أو إخفاقات غير متوقعة أخرى.
قد تتسبب الاعتمادات الدائرية أيضاً في حدوث تسريبات للذاكرة (memory leaks) عن طريق منع بعض أنظمة جمع المهملات التلقائية (تلك التي تستخدم عد المراجع reference counting) من إلغاء تخصيص الكائنات غير المستخدمة.
الأسباب والحلول
في تصميمات البرمجيات الكبيرة جداً، قد يفقد مهندسو البرمجيات السياق ويقومون عن غير قصد بإدخال اعتمادات دائرية. وهناك أدوات لتحليل البرمجيات والعثور على الاعتمادات الدائرية غير المرغوب فيها.
يمكن إدخال اعتمادات دائرية عند تطبيق وظيفة دالة الاستدعاء الراجع (callback). ويمكن تجنب ذلك عن طريق تطبيق أنماط التصميم (design patterns) مثل نمط المراقب (observer pattern).
مثال
تحتاج OrderService إلى Billing لتحصيل ثمن الطلب، بينما تستدعي Billing بدورها OrderService لتعليم الطلب بأنه مدفوع. لا يمكن ترجمة أي من الكلاسين أو اختباره أو إعادة استخدامه دون الآخر.
class OrderService {
private final Billing billing = new Billing(this);
void place(Order order) { billing.charge(order); }
void markPaid(Order order) { order.paid = true; }
}
class Billing {
private final OrderService orders;
Billing(OrderService orders) { this.orders = orders; }
void charge(Order order) {
// ... خصم المبلغ من البطاقة ...
orders.markPaid(order); // تستدعي Billing كلاس OrderService من جديد
}
}
يمرّ الاستدعاء الراجع عبر واجهة صغيرة تملكها وحدة الفوترة، كما في نمط المراقب:
interface PaymentListener {
void paid(Order order);
}
class Billing {
private final PaymentListener listener;
Billing(PaymentListener listener) { this.listener = listener; }
void charge(Order order) {
// ... خصم المبلغ من البطاقة ...
listener.paid(order);
}
}
class OrderService implements PaymentListener {
private final Billing billing = new Billing(this);
void place(Order order) { billing.charge(order); }
@Override
public void paid(Order order) { order.paid = true; }
}
أصبحت Billing تعتمد على PaymentListener فقط، فصار اتجاه الاعتماد بين الوحدتين واحداً: الطلبات ← الفوترة.
مقتبس من مقالة ويكيبيديا “Circular dependency” (المراجعة 1303252600)، والمتاحة بموجب رخصة CC BY-SA 4.0؛ تمت ترجمة النص وتحريره وإضافة أمثلة.