حقن التبعية


في هندسة البرمجيات ، يُعدّ حقن التبعية أسلوبًا برمجيًا يستقبل فيه كائن أو دالة كائنات أو دوال أخرى يحتاجها، بدلًا من إنشائها داخليًا. يهدف حقن التبعية إلى فصل مهام إنشاء الكائنات عن مهام استخدامها، مما يؤدي إلى برامج ذات ترابط ضعيف . [ 1 ] [ 2 ] [ 3 ] يضمن هذا النمط أن الكائن أو الدالة التي ترغب في استخدام خدمة معينة لا تحتاج إلى معرفة كيفية إنشاء تلك الخدمات. بدلًا من ذلك، يتم تزويد " العميل " (الكائن أو الدالة) المُستقبِل بتبعياته من خلال كود خارجي (يُسمى "الحاقن")، دون علمه. [ 4 ] يُوضّح حقن التبعية التبعيات الضمنية ويُساعد في حل المشكلات التالية: [ 5 ]
- كيف يمكن لفئة أن تكون مستقلة عن إنشاء الكائنات التي تعتمد عليها؟
- كيف يمكن لتطبيق ما وللكائنات التي يستخدمها أن تدعم تكوينات مختلفة؟
يُستخدم حقن التبعية غالبًا للحفاظ على توافق الكود مع مبدأ عكس التبعية . [ 6 ] [ 7 ]
في اللغات ذات الكتابة الثابتة، يعني استخدام حقن التبعية أن العميل يحتاج فقط إلى تعريف واجهات الخدمات التي يستخدمها، بدلاً من تطبيقاتها الملموسة، مما يسهل تغيير الخدمات المستخدمة في وقت التشغيل دون إعادة التجميع.
غالبًا ما تجمع أطر عمل التطبيقات بين حقن التبعية وعكس التحكم . في ظل عكس التحكم، يقوم إطار العمل أولًا بإنشاء كائن (مثل وحدة تحكم)، ثم يمرر تدفق التحكم إليه. أما في ظل حقن التبعية، فيقوم إطار العمل أيضًا بإنشاء التبعيات المُعلنة بواسطة كائن التطبيق (غالبًا في معلمات دالة البناء)، ثم يمرر هذه التبعيات إلى الكائن. [ 8 ]
تُطبّق تقنية حقن التبعية فكرة "عكس التحكم في تنفيذ التبعيات"، ولهذا السبب تُطلق بعض أطر عمل جافا على هذا المفهوم اسم "عكس التحكم" (لا ينبغي الخلط بينه وبين عكس تدفق التحكم ). [ 9 ]
الأدوار
حقنة الإدمان للأطفال في سن الخامسة
عندما تذهب وتأخذ أشياءً من الثلاجة لنفسك، قد تُسبب مشاكل. قد تترك الباب مفتوحًا، أو قد تأخذ شيئًا لا يُريده والدك أو والدتك. بل قد تبحث عن شيء غير موجود لدينا أو منتهي الصلاحية.
ما يجب عليك فعله هو التعبير عن حاجتك، "أحتاج إلى شيء أشربه مع الغداء"، ثم سنتأكد من حصولك على شيء ما عندما تجلس لتناول الطعام.
تتضمن عملية حقن التبعية أربعة أدوار: الخدمات، والعملاء، والواجهات، والحاقنات.
الخدمات والعملاء
الخدمة هي أي فئة تحتوي على وظائف مفيدة. وبالمقابل، العميل هو أي فئة تستخدم الخدمات. والخدمات التي يحتاجها العميل هي تبعياته .
يمكن لأي كائن أن يكون خدمة أو عميلًا؛ وتتعلق هذه التسميات فقط بالدور الذي تؤديه الكائنات في عملية الحقن. بل قد يكون الكائن نفسه عميلًا (يستخدم الخدمات المحقونة) وخدمة في الوقت نفسه (يتم حقنه في كائنات أخرى). عند الحقن، تُصبح الخدمة جزءًا من حالة العميل ، ومتاحة للاستخدام. [ 12 ]
واجهات
لا ينبغي للعملاء معرفة كيفية تنفيذ تبعياتهم، بل فقط أسمائها وواجهة برمجة التطبيقات (API) . على سبيل المثال، قد تستخدم خدمة استرجاع رسائل البريد الإلكتروني بروتوكولات IMAP أو POP3 في الخلفية، ولكن من المرجح أن تكون هذه التفاصيل غير ذات صلة بالبرنامج الذي يستدعي ببساطة استرجاع بريد إلكتروني. بتجاهل تفاصيل التنفيذ، لا يحتاج العملاء إلى التغيير عند تغيير تبعياتهم.
حاقنات الوقود
يقوم المُحقن ، والذي يُطلق عليه أحيانًا اسم المُجمِّع أو الحاوية أو المُزوِّد أو المصنع، بتقديم الخدمات إلى العميل.
يتمثل دور المُحقِّنات في بناء وربط مخططات الكائنات المعقدة، حيث قد تكون الكائنات عملاء وخدمات في آنٍ واحد. قد يتكون المُحقِّن نفسه من عدة كائنات تعمل معًا، ولكن لا يجب أن يكون هو العميل، لأن ذلك سيؤدي إلى إنشاء تبعية دائرية .
لأن حقن التبعية يفصل بين كيفية إنشاء الكائنات وكيفية استخدامها، فإنه غالبًا ما يقلل من أهمية الكلمة newالمفتاحية الموجودة في معظم لغات البرمجة كائنية التوجه . ولأن الإطار البرمجي يتولى إنشاء الخدمات، يميل المبرمج إلى إنشاء كائنات القيمة مباشرةً فقط ، والتي تمثل الكيانات في نطاق البرنامج (مثل Employeeكائن في تطبيق أعمال أو Orderكائن في تطبيق تسوق). [ 13 ] [ 14 ] [ 15 ] [ 16 ]
التشبيه
على سبيل المثال، يمكن اعتبار السيارات خدمات تؤدي مهمة نقل الأشخاص من مكان إلى آخر. قد تحتاج محركات السيارات إلى البنزين أو الديزل أو الكهرباء ، لكن هذه التفاصيل غير مهمة للراكب الذي لا يهمه سوى الوصول إلى وجهته.
تتميز السيارات بواجهة موحدة من خلال دواساتها وعجلة القيادة وغيرها من أدوات التحكم. ولذلك، فإن نوع المحرك الذي تم تركيبه في المصنع لم يعد مهماً، ويمكن للسائقين التبديل بين أي نوع من السيارات حسب الحاجة.
المزايا والعيوب
المزايا
تتمثل إحدى الفوائد الأساسية لحقن التبعية في تقليل الترابط بين الفئات وتبعياتها. [ 17 ] [ 18 ]
بإزالة معرفة العميل بكيفية تنفيذ تبعياته، تصبح البرامج أكثر قابلية لإعادة الاستخدام والاختبار والصيانة. [ 19 ]
وينتج عن ذلك أيضًا زيادة في المرونة: إذ يمكن للعميل أن يتصرف بناءً على أي شيء يدعم الواجهة الجوهرية التي يتوقعها. [ 20 ]
وبشكل عام، يقلل حقن التبعية من الشيفرة النمطية ، حيث تتم معالجة جميع عمليات إنشاء التبعيات بواسطة مكون واحد. [ 19 ]
وأخيرًا، يسمح حقن التبعية بالتطوير المتزامن. إذ يمكن لمطورين اثنين تطوير فئات تستخدم بعضها البعض بشكل مستقل، مع الحاجة فقط إلى معرفة الواجهة التي ستتواصل من خلالها هذه الفئات. غالبًا ما يتم تطوير الإضافات بواسطة جهات خارجية لا تتواصل أبدًا مع مطوري المنتج الأصلي. [ 21 ]
الاختبار
تُعد العديد من فوائد حقن التبعية ذات صلة خاصة باختبار الوحدات .
على سبيل المثال، يمكن استخدام حقن التبعية لفصل تفاصيل تكوين النظام في ملفات تكوين خارجية، مما يسمح بإعادة تكوين النظام دون الحاجة إلى إعادة تجميعه. ويمكن كتابة تكوينات منفصلة لحالات مختلفة تتطلب تطبيقات مختلفة للمكونات. [ 22 ]
وبالمثل، ولأن حقن التبعية لا يتطلب أي تغيير في سلوك الكود، يمكن تطبيقه على الكود القديم كعملية إعادة هيكلة . وهذا يجعل العملاء أكثر استقلالية ويسهل اختبارهم بشكل منفصل، باستخدام نماذج أولية أو كائنات وهمية تحاكي الكائنات الأخرى غير الخاضعة للاختبار.
تُعد سهولة الاختبار هذه غالباً أول فائدة تُلاحظ عند استخدام حقن التبعية. [ 23 ]
العيوب
يجادل منتقدو حقن التبعية بأنها:
- يُنشئ عملاء يطلبون تفاصيل التكوين، وهو ما قد يكون مرهقًا عندما تكون الإعدادات الافتراضية الواضحة متاحة. [ 21 ]
- يجعل ذلك تتبع الكود صعباً لأنه يفصل بين السلوك والبناء. [ 21 ]
- يتم تنفيذه عادةً باستخدام الانعكاس أو البرمجة الديناميكية، مما يعيق أتمتة بيئة التطوير المتكاملة . [ 24 ]
- يتطلب ذلك عادةً جهداً أكبر في التطوير المسبق. [ 25 ]
- يشجع على الاعتماد على إطار عمل. [ 26 ] [ 27 ] [ 28 ]
أنواع حقن التبعية
هناك عدة طرق يمكن للعميل من خلالها تلقي الخدمات المحقونة: [ 29 ]
- حقن المُنشئ، حيث يتم توفير التبعيات من خلال مُنشئ فئة العميل .
- حقن الأساليب، حيث يتم توفير التبعيات لأسلوب ما فقط عند الحاجة إليها لوظائف محددة.
- حقن المُعدِّل، حيث يُعرِّض العميل طريقة مُعدِّل تقبل التبعية.
- حقن الواجهة، حيث توفر واجهة التبعية طريقة حقن تقوم بحقن التبعية في أي عميل يتم تمريره إليها.
في بعض الأطر البرمجية، لا يحتاج العملاء إلى قبول حقن التبعية بشكل صريح على الإطلاق. في لغة جافا ، على سبيل المثال، يمكن للانعكاس أن يجعل السمات الخاصة عامة عند الاختبار وحقن الخدمات مباشرة. [ 30 ]
بدون حقن التبعية
في مثال جافا التالي ، Clientيحتوي الصنف على Serviceمتغير عضو مُهيأ في الدالة البانية . يقوم العميل مباشرةً بإنشاء الخدمة التي يستخدمها والتحكم بها، مما يُنشئ تبعية مُضمنة في الكود.
public class Client { private Service service ;Client ( ) { // التبعية مُضمنة في الكود. this.service = new ExampleService (); } }حقن المُنشئ
أكثر أشكال حقن التبعية شيوعًا هو أن تطلب الفئة تبعياتها من خلال مُنشئها . وهذا يضمن أن يكون العميل دائمًا في حالة صالحة، لأنه لا يمكن إنشاء مثيل له بدون تبعياته الضرورية.
public class Client { private Service service ;// يتم حقن التبعية من خلال المُنشئ. العميل ( الخدمة الخدمة ) { إذا ( الخدمة == فارغة ) { ارمِ استثناءً غير قانوني ( "يجب ألا تكون الخدمة فارغة" ); } هذا.الخدمة = الخدمة ؛ } }تجنب الأشخاص المنفردين
يمكن استخدام حقن التبعية لتجنب نمط الكائن الأحادي . يفرض نمط الكائن الأحادي قيدًا هيكليًا يتمثل في وجود نسخة واحدة فقط من الفئة، بينما يركز حقن التبعية على كيفية تمرير التبعيات، مما يجنب الكائنات الحاجة إلى البحث عن حالتها العامة. [ 31 ]
على سبيل المثال، في المثال التالي، OrderServiceيرتبط هذا ارتباطًا وثيقًا بـ DatabaseConnection؛ لا يمكن استبدال قاعدة البيانات ولا يمكن استخدام مثيل مختلف.
// فئة أحادية ملموسة class DatabaseConnection { private static DatabaseConnection instance ;private DatabaseConnection () { // تهيئة موارد قاعدة البيانات }public static synchronized DatabaseConnection instance () { if ( instance == null ) { instance = new DatabaseConnection (); } return instance ; }public void executeQuery ( String sql ) { System . out . printf ( "Executing: %s%n" ); } }// فئة مستهلك مترابطة بإحكام class OrderService { public void placeOrder ( String orderId ) { // البحث عن التبعيات المضمن يجعل اختبار الوحدة مستحيلاً DatabaseConnection db = DatabaseConnection . instance (); db . executeQuery ( String . format ( "INSERT INTO orders VALUES ('%s')" )); } }يتجنب حقن التبعية السؤال عن كيفية إنشاء قاعدة البيانات، أو عدد النسخ الموجودة، ويطلب فقط الواجهة Databaseفي مُنشئها:
// واجهة مجردة interface Database { void executeQuery ( String sql ) ; }// تنفيذ الفئة الملموسة (بدون منطق Singleton) class SqlDatabase implements Database { @Override public void executeQuery ( String sql ) { System . out . println ( "Executing SQL: %s" , sql ); } }// تقبل فئة المستهلك التبعية عبر حقن المُنشئ class OrderService { private final Database db ;// يتم حقن التبعية من الخارج public OrderService ( Database db ) { this . db = db ; }public void placeOrder ( String orderId ) { db . executeQuery ( String . format ( "INSERT INTO orders VALUES ('%s')" , orderId )); } }طريقة الحقن
تُمرَّر التبعيات كوسائط إلى دالة محددة، مما يسمح باستخدامها فقط أثناء تنفيذ تلك الدالة دون الحاجة إلى الاحتفاظ بمرجع طويل الأمد. يُعد هذا الأسلوب مفيدًا بشكل خاص للتبعيات المؤقتة أو عند الحاجة إلى تطبيقات مختلفة لاستدعاءات دوال متعددة.
public class Client { public void performAction ( Service service ) { if ( service == null ) { throw new IllegalArgumentException ( "يجب ألا تكون قيمة service فارغة" ); } service . execute (); } }حقنة سيتر
من خلال قبول التبعيات عبر دالة الضبط (setter method ) بدلاً من دالة البناء (constructor)، يُمكن للعملاء السماح للمُحقِّنات (exjectors) بالتحكم في تبعياتهم في أي وقت. يوفر هذا مرونة، ولكنه يُصعِّب ضمان حقن جميع التبعيات وصلاحيتها قبل استخدام العميل.
public class Client { private Service service ;// يتم حقن التبعية من خلال دالة setter. public void setService ( Service service ) { if ( service == null ) { throw new IllegalArgumentException ( "يجب ألا تكون قيمة service فارغة" ); } this . service = service ; } }حقن الواجهة
مع حقن الواجهة، تكون التبعيات جاهلة تمامًا بعملائها، ومع ذلك لا تزال ترسل وتستقبل مراجع لعملاء جدد.
وبهذه الطريقة، تصبح التبعيات بمثابة مُحقِّنات. والأساس هو أن طريقة الحقن تُوفَّر من خلال واجهة.
لا يزال هناك حاجة إلى مُجمِّع لتقديم العميل وتوابعه. يأخذ المُجمِّع مرجعًا إلى العميل، ويُحوِّله إلى واجهة الضبط التي تُعيِّن تلك التبعية، ثم يُمرِّره إلى كائن التبعية الذي بدوره يُمرِّر مرجعًا إلى نفسه إلى العميل.
لكي يكون لحقن الواجهة قيمة، يجب أن يقوم التابع بوظيفة إضافية إلى جانب مجرد إرجاع مرجع لنفسه. قد يكون ذلك العمل كعامل مصنع أو مُجمِّع فرعي لحل التبعيات الأخرى، وبالتالي تجريد بعض التفاصيل من المُجمِّع الرئيسي. وقد يكون ذلك عدّ المراجع لكي يعرف التابع عدد العملاء الذين يستخدمونه. إذا كان التابع يحتفظ بمجموعة من العملاء، فيمكنه لاحقًا حقنهم جميعًا بنسخة مختلفة منه.
package org.wikipedia.examples ;استيراد java.util.HashSet ؛ استيراد java.util.Set ؛واجهة ServiceSetter { void setService ( Service service ); }class Client implements ServiceSetter { private Service service ;@Override public void setService ( Service service ) { if ( service == null ) { throw new IllegalArgumentException ( "يجب ألا تكون قيمة service فارغة" ); } this . service = service ; } }class ServiceInjector { private final Set < ServiceSetter > clients = new HashSet <> ();public void inject ( ServiceSetter client ) { this . clients . add ( client ); client . setService ( new ExampleService ()); }public void switch () { for ( Client client : this . clients ) { client . setService ( new AnotherExampleService ()); } } }class ExampleService implements Service {}class AnotherExampleService implements Service {}حَشد
أبسط طريقة لتنفيذ حقن التبعية هي ترتيب الخدمات والعملاء يدويًا، ويتم ذلك عادةً في جذر البرنامج، حيث يبدأ التنفيذ.
public class Program { public static void main ( String [] args ) { // إنشاء الخدمة. Service service = new ExampleService ();// حقن الخدمة في العميل. Client client = new Client ( service );// استخدم الكائنات . System.out.println ( client.greet ( ) ) ; } }قد يكون البناء اليدوي أكثر تعقيدًا ويتضمن بناة أو مصانع أو أنماط بناء أخرى .
الأطر

غالبًا ما يكون حقن التبعيات يدويًا عمليةً شاقةً وعرضةً للأخطاء في المشاريع الكبيرة، مما يشجع على استخدام أطر العمل التي تُؤتمت هذه العملية. يتحول حقن التبعيات اليدوي إلى إطار عمل لحقن التبعيات عندما يصبح الكود المُنشئ عامًا وليس خاصًا بالتطبيق. [ 32 ] مع أن هذه الأدوات مفيدة، إلا أنها ليست ضرورية لتنفيذ حقن التبعيات. [ 33 ] [ 34 ]
في لغة جافا، تم توفير إطار عمل حقن التبعية تاريخيًا بواسطة Java EE تحت اسم javax.inject; يتم توفيره الآن بواسطة Jakarta EE باسم jakarta.inject، مع jakarta.inject.Provider<T>واجهة للحصول على مثيلات من ، والتعليقات التوضيحيةT التالية : [ 35 ]
@Inject، لتمييز المُنشئات والأساليب والحقول القابلة للحقن@Named، لتمييز مُؤهِّل قائم على السلسلة@Qualifier، لتمييز التعليقات التوضيحية المؤهلة@Scope، لتمييز التعليقات التوضيحية للنطاق@Singleton، لتمييز نوع يخضع لنمط المفرد بواسطة المُحقِّن
في بيئة .NET (مثل C# )، Microsoft.Extensions.DependencyInjectionتكون المكتبة عبارة عن إطار عمل مدمج داخل .NET. [ 36 ]
يمكن لبعض الأطر، مثل Spring ، استخدام ملفات التكوين الخارجية لتخطيط تكوين البرنامج:
package org.wikipedia.examples ;استيراد org.springframework.beans.factory.BeanFactory ؛ استيراد org.springframework.context.ApplicationContext ؛ استيراد org.springframework.context.support.ClassPathXmlApplicationContext ؛public class Injector { public static void main ( String [] args ) { // يتم تخزين تفاصيل الخدمة المحددة المراد استخدامها في ملف تكوين منفصل عن البرنامج نفسه. BeanFactory beanfactory = new ClassPathXmlApplicationContext ( "Beans.xml" ); Client client = ( Client ) beanfactory . getBean ( "client" ); System . out . println ( client . greet ()); } }حتى مع وجود مخطط كائنات طويل ومعقد محتمل، فإن الفئة الوحيدة المذكورة في الكود هي نقطة الدخول، في هذه الحالة Client. Clientلم تخضع لأي تغييرات للعمل مع Spring، وتبقى كائن POJO . [ 37 ] [ 38 ] [ 39 ] من خلال منع انتشار التعليقات التوضيحية والاستدعاءات الخاصة بـ Spring بين العديد من الفئات، يظل النظام معتمدًا بشكل طفيف فقط على Spring. [ 27 ]
أمثلة
زاوية
يوضح المثال التالي مكون Angular الذي يستقبل خدمة الترحيب من خلال حقن التبعية:
/* greeter.service.ts */ import { Injectable , inject } from '@angular/core' ; import { DOCUMENT } from '@angular/common' ;export interface IGreeterService { greet ( text : string ) : void ; }@Injectable ({ providedIn : 'root' }) export class GreeterService implements IGreeterService { // استخدم inject(DOCUMENT) وقم بالوصول إلى defaultView للحصول على كائن Window private window : Window = inject ( DOCUMENT ) .defaultView as Window ;greet ( text : string ) : void { this.window.alert ( text ) ; } }<!-- my-controller.component.html --> < div > < button ( click )=" sayHello ()" > مرحباً </ button > </ div >/* my-controller.component.ts */ import { GreeterService } from './greeter.service.ts' ; import { Component , inject } from '@angular/core' ; @Component ({ selector : 'app-my-controller' , template : 'my-controller.component.html' }) export class MyControllerComponent {private greeter = inject ( GreeterService );sayHello ( ) : void { this.greeter.greet ( ' Hello World' ) ; } }تقوم الدالة injectبتشغيل المُحقِّن لإنشاء نسخة من الكائن GreeterServiceوتوابعه (في هذه الحالة، Documentنسخة للوصول إلى windowالكائن).
لغة سي++
يقدم هذا المثال نموذجًا لحقن المُنشئ.
استيراد std ؛class DatabaseConnection { public : void connect () { std :: println ( "جاري الاتصال بقاعدة البيانات..." ); } };class DatabaseService { private : DatabaseConnection & dbConn ; public : explicit DatabaseService ( DatabaseConnection & db ) : dbConn { db } {}void execute () { dbConn . connect (); std :: println ( "جاري تنفيذ خدمة قاعدة البيانات..." ); } };int main ( int argc , char * argv []) { DatabaseConnection db ; DatabaseService sv ( db ); sv . execute (); }يقدم هذا المثال نموذجًا لحقن الواجهة.
استيراد std ؛باستخدام std :: expected ؛ باستخدام std :: shared_ptr ؛ باستخدام std :: expected ؛class IConnection { public : enum class Error { NO_CONNECTION , // المزيد من الأخطاء هنا };virtual void connect () = 0 ; virtual ~ IConnection () = default ; };class DatabaseConnection : public IConnection { public : DatabaseConnection () = default ;void connect () override { std :: println ( "جاري الاتصال بقاعدة البيانات..." ); } };class DatabaseService { private : shared_ptr < IConnection > conn ; public : DatabaseService () = default ;void setConnection ( shared_ptr < IConnection > nextConn ) noexcept { conn = nextConn ; }expected < void , IConnection :: Error > execute () { if ( conn ) { conn -> connect (); std :: println ( "جاري تنفيذ خدمة قاعدة البيانات..." ); } else { return unexpected ( IConnection :: Error :: NO_CONNECTION ); } } };int main ( int argc , char * argv []) { shared_ptr < DatabaseConnection > db = std :: make_shared < DatabaseConnection > (); DatabaseService sv ; sv . setConnection ( db ); sv . execute (); }تاريخيًا، ولأن لغة C++ كانت تفتقر إلى خاصية الانعكاس ، لم تتمكن المكتبات من "رؤية" توقيعات الدوال البانية أو أنواع أعضاء الفئات، مما أجبر على استخدام حقن التبعية اليدوي أو مكتبات البرمجة الوصفية مثل Boost.DI لربط الوسائط بحقن التبعية يدويًا. [ 40 ] وقد تم تبسيط هذا الأمر بشكل كبير باستخدام خاصية انعكاس معلمات الدوال والتعليقات التوضيحية في C++26 . [ 41 ] [ 42 ]
استيراد std ؛باستخدام std :: string ;class IDatabase { /* ... */ }; class FastDatabase : public IDatabase { /* ... */ }; class BasicDatabase : public IDatabase { /* ... */ };class Foo { private : // ... public : [[ = Inject ]] Foo ( const IDatabase & primary , const IDatabase & secondary , const string & s , int i ) { // ... } };class Bar { private : // ... public : [[ = Inject ]] Bar ( const IDatabase & primary , int i ) { // ... } };Injector <IDatabase> injector = Injector <IDatabase> :: Builder () . addService ( " primary" , std :: make_unique <FastDatabase> ( ) ) . addService ( "secondary" , std :: make_unique <BasicDatabase> ( ) ) . build ( ) ;Foo foo = injector.build <Foo> ( " abc " , 1 ) ; Bar bar = injector.build <Bar> ( 2 ) ;سي شارب
يقدم هذا المثال نموذجًا لحقن المُنشئ في لغة C# .
namespace Wikipedia.Examples ;باستخدام النظام ؛// لن يعرف عميلنا سوى هذه الواجهة، وليس نوع لوحة الألعاب التي يستخدمها. interface IGamePadFunctionality { string GetGamePadName (); void SetVibrationPower ( float power ); }// توفر الخدمات التالية تطبيقات عملية للواجهة المذكورة أعلاه.class XboxGamePad : IGamePadFunctionality { float vibrationPower = 1.0f ; public string GetGamePadName () => "Xbox controller" ; public void SetVibrationPower ( float power ) => this . vibrationPower = Math . Clamp ( power , 0.0f , 1.0f ); }class PlayStationJoystick : IGamePadFunctionality { float vibratingPower = 100.0f ; public string GetGamePadName () => "PlayStation controller" ; public void SetVibrationPower ( float power ) => this . vibratingPower = Math . Clamp ( power * 100.0f , 0.0f , 100.0f ); }class SteamController : IGamePadFunctionality { double vibrating = 1.0 ; public string GetGamePadName () => "Steam controller" ; public void SetVibrationPower ( float power ) => this . vibrating = Convert . ToDouble ( Math . Clamp ( power , 0.0f , 1.0f )); }// هذه الفئة هي العميل الذي يتلقى الخدمة. class GamePad { IGamePadFunctionality gamePadFunctionality ;// يتم حقن الخدمة من خلال المُنشئ وتخزينها في الحقل أعلاه. public GamePad ( IGamePadFunctionality gamePadFunctionality ) => this . gamePadFunctionality = gamePadFunctionality ;public void Showcase () { // يتم استخدام الخدمة المُضافة. string gamePadName = this . gamePadFunctionality . GetGamePadName (); string message = $"نحن نستخدم {gamePadName} الآن، هل تريد تغيير قوة الاهتزاز؟" ; Console . WriteLine ( message ); } }class Program { static void Main ( string [] args ) { SteamController steamController = new (); // كان بإمكاننا أيضًا تمرير XboxController أو PlayStationJoystick، إلخ. // لا تعرف وحدة التحكم ما تستخدمه ولا تحتاج إلى ذلك. GamePad gamepad = new ( steamController ); gamepad . Showcase (); } }يذهب
لا تدعم لغة Go الأصناف، وعادةً ما يتم تجريد حقن التبعية إما بواسطة مكتبة مخصصة تستخدم الانعكاس أو الأنواع العامة (التي أصبحت مدعومة منذ Go 1.18 [ 43 ] ). [ 44 ] ويُوضح المثال التالي لتطبيق ويب MVC مثالًا أبسط لا يستخدم مكتبات حقن التبعية.
أولاً، قم بتمرير التبعيات اللازمة إلى جهاز التوجيه، ثم من جهاز التوجيه إلى وحدات التحكم:
جهاز توجيه الطروداستيراد ( "database/sql" "net/http""مثال/وحدات التحكم/المستخدمون""github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware""github.com/redis/go-redis/v9" "github.com/rs/zerolog" )type RoutingHandler struct { // تمرير القيم بواسطة مؤشر إلى أسفل مكدس الاستدعاءات // يعني أننا لن ننشئ نسخة جديدة، مما يوفر الذاكرة log * zerolog . Logger db * sql . DB cache * redis . Client router chi . Router }// عادةً ما يتم تهيئة الاتصال، والمسجل، وذاكرة التخزين المؤقت في الدالة الرئيسية func NewRouter ( log * zerolog . Logger , db * sql . DB , cache * redis . Client , ) ( r * RoutingHandler ) { rtr := chi . NewRouter ()return & RoutingHandler { log : log , db : db , cache : cache , router : rtr , } }func ( r * RoutingHandler ) SetupUsersRoutes () { uc := users . NewController ( r . log , r . db , r . cache )r.router.Get ( " / users / : name " , func ( w http.ResponseWriter , r * http.Request ) { uc.Get ( w , r ) } ) }بعد ذلك، يمكن الوصول إلى الحقول الخاصة بالبنية في أي طريقة هي مستقبل المؤشر الخاص بها ، دون انتهاك التغليف.
مستخدمو الحزمةاستيراد ( "database/sql" "net/http""example/models""github.com/go-chi/chi/v5" "github.com/redis/go-redis/v9" "github.com/rs/zerolog" )نوع وحدة التحكم struct { سجل * zerolog.Logger تخزين نماذج . UserStorage ذاكرة تخزين مؤقتة * redis.Client }func NewController ( log * zerolog.Logger , db * sql.DB , cache * redis.Client ) * Controller { return & Controller { log : log , storage : models.NewUserStorage ( db ) , cache : cache , } }func ( uc * Controller ) Get ( w http . ResponseWriter , r * http . Request ) { // لاحظ أنه يمكننا أيضًا تغليف عملية التسجيل في برنامج وسيط، هذا لأغراض التوضيح فقط uc . log . Info (). Msg ( "Getting user" )userParam := تشي . URLParam ( ص , "اسم" )var user * models . User // استرجاع المستخدم من ذاكرة التخزين المؤقت err := uc . cache . Get ( r . Context (), userParam ). Scan ( & user ) if err != nil { uc . log . Error (). Err ( err ). Msg ( "خطأ في استرجاع المستخدم من ذاكرة التخزين المؤقت. جارٍ الاسترجاع من قاعدة بيانات SQL" ) }user , err = uc.storage.Get ( r.Context ( ), "johndoe" ) if err != nil { uc.log.Error ( ) . Err ( err ) .Msg ( " خطأ في جلب المستخدم من قاعدة بيانات SQL " ) http.Error ( w , " خطأ داخلي في الخادم " , http.StatusInternalServerError ) return } }وأخيرًا، يمكن استخدام اتصال قاعدة البيانات الذي تم تهيئته في الدالة الرئيسية في طبقة الوصول إلى البيانات:
نماذج التغليفاستيراد ( "database/sql" "time" )نوع ( UserStorage struct { conn * sql . DB }بنية المستخدم { الاسم سلسلة نصية ' json : "name" db : "name,primarykey" ' تاريخ الانضمام وقت . الوقت ' json : "joined_at" db : "joined_at" ' البريد الإلكتروني سلسلة نصية ' json : "email" db : "email" ' } )func NewUserStorage ( conn * sql.DB ) * UserStorage { return & UserStorage { conn : conn , } }func ( us * UserStorage ) Get ( name string ) ( user * User , err error ) { // بافتراض أن 'name' هو مفتاح فريد query := "SELECT * FROM users WHERE name = $1"إذا حدث خطأ أثناء استدعاء الدالة `us.conn.QueryRow ( query , name ) .Scan ( & user ) ` ، وكان هذا الخطأ غير فارغ، فسيتم إرجاع قيمة فارغة مع الخطأ .return user , nil }الصدأ
في لغة Rust ، يتم عادةً حقن التبعية من خلال الأنواع العامة مع الإرسال الثابت .
trait Logger { fn log ( & self , message : & str ); }struct ConsoleLogger ;impl Logger for ConsoleLogger { fn log ( & self , message : & str ) { println! ( "{message}" ); } }struct UserService < L : Logger > { logger : L , }impl < L : Logger > UserService <L> { fn new ( logger : L ) - > Self { Self { logger } }fn create_user ( & self , name : & str ) { self.logger.log ( & format ! ( " إنشاء المستخدم: {name}" ) ) ; // منطق العمل... } }fn main () { let logger = ConsoleLogger ; let service = UserService :: new ( logger );service.create_user ( "Alice " ) ; }ومع ذلك، يمكن القيام بذلك أيضًا باستخدام الإرسال الديناميكي مع كائنات السمات:
trait Logger { fn log ( & self , message : & str ); }struct ConsoleLogger ;impl Logger for ConsoleLogger { fn log ( & self , message : & str ) { println! ( "{message}" ); } }struct UserService { logger : Box < dyn Logger > , }impl UserService { fn new ( logger : Box < dyn Logger > ) -> Self { Self { logger } }fn create_user ( & self , name : & str ) { self.logger.log ( & format ! ( "إنشاء المستخدم: {name} " ) ) ; } }fn main () { let logger = Box :: new ( ConsoleLogger ); let service = UserService :: new ( logger );service.create_user ( "Alice " ) ; }انظر أيضاً
مراجع
- ↑ سيمان، مارك. "حقن التبعية هو اقتران فضفاض" . blog.ploeh.dk . تم الاسترجاع في 28 يوليو 2015 .
- 1 2 سيمان، مارك (أكتوبر 2011). حقن التبعية في .NET . منشورات مانينغ. ص 4. ISBN 9781935182504.
- ↑ نيكو شوارتز، ميرسيا لونغو، أوسكار نيرستراز، "سوس: فصل المسؤوليات عن الأساليب الثابتة من أجل قابلية التكوين الدقيقة"، مجلة تكنولوجيا الكائنات، المجلد 11، العدد 1 (أبريل 2012)، الصفحات 3:1-23.
- ↑ "HollywoodPrinciple" . c2.com . تم الاطلاع عليه بتاريخ 19-07-2015 .
- ↑ "نمط تصميم حقن التبعية - المشكلة والحل والتطبيق" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
- ↑ إيريز، جاي (9 مارس 2022). "عكس التبعية مقابل حقن التبعية" . ميديوم . تم الاسترجاع في 6 ديسمبر 2022 .
- ↑ ماثيوز، ساشا (25 مارس 2021). "أنت ببساطة تُدخل تبعية، معتقدًا أنك تتبع مبدأ عكس التبعية..." موقع Medium . تم الاطلاع عليه بتاريخ 6 ديسمبر 2022 .
- ↑ "حاوية Spring IoC" . تم الاسترجاع في 23-05-2023 .
- ↑ فاولر، مارتن. "حاويات عكس التحكم ونمط حقن التبعية" . MartinFowler.com . تم الاطلاع عليه بتاريخ 4 يونيو 2023 .
- ↑ "حقن التبعية في NET" (ملف PDF) . philkildea.co.uk . ص 4. مؤرشف من الأصل (ملف PDF) بتاريخ 21-07-2015 . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ "كيف تشرح مفهوم حقن التبعية لطفل عمره 5 سنوات؟" . stackoverflow.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ تكنولوجيا المعلومات، تيتانيوم. "جيمس شور: تبسيط مفهوم حقن التبعية" . www.jamesshore.com . تاريخ الاسترجاع: 18 يوليو 2015 .
- ↑ "أن تكون "جديداً" أم لا..." مؤرشف من الأصل بتاريخ 13 مايو 2020. تم الاطلاع عليه بتاريخ 18 يوليو 2015 .
- ↑ "كيفية كتابة كود قابل للاختبار" . www.loosecouplings.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ "كتابة كود نظيف وقابل للاختبار" . www.ethanresnick.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ سيروني، جورجيو. "متى يتم الحقن: الفرق بين العلاجات الجديدة والحقن - غير مرئي للعين" . www.giorgiosironi.com . تاريخ الاسترجاع: 18 يوليو 2015 .
- ↑ "الكندي الحضري، أليس كذلك: حول حقن التبعية وانتهاك مخاوف التغليف" . www.bryancook.net . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ "نمط تصميم حقن التبعية" . msdn.microsoft.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- 1 2 "برنامج عملية مجتمع جافا (SM) - طلبات مواصفات جافا (JSRs): تفاصيل JSR# 330" . jcp.org . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ "3.1. حقن التبعية - بايثون 3: من لا شيء إلى التعلم الآلي" . مؤرشف من الأصل بتاريخ 2020-02-08.
- 1 2 3 "كيف يعمل حقن التبعية (DI) في تطوير تطبيقات Spring Java - DZone Java" .
- ↑ "حقن التبعية وعكس التحكم في بايثون - وثائق Dependency Injector 4.36.2" .
- ↑ "كيفية إعادة هيكلة حقن التبعية، الجزء 3: التطبيقات الأكبر حجماً" .
- ↑ "مقدمة سريعة عن حقن التبعية: ما هو، ومتى يُستخدم" . 18 أكتوبر 2018.
- ↑ "حقن التبعية | Professionalqa.com" .
- ↑ "ما هي سلبيات استخدام حقن التبعية؟" . stackoverflow.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- 1 2 "عكس حقن التبعية - المبرمج النظيف" . sites.google.com . تم الاسترجاع في 18-07-2015 .
- ↑ "فصل تطبيقك عن إطار عمل حقن التبعية" . InfoQ . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ مارتن فاولر (23 يناير 2004). "حاويات عكس التحكم ونمط حقن التبعية - أشكال حقن التبعية" . Martinfowler.com . تاريخ الاسترجاع: 22 مارس 2014 .
- ↑ "AccessibleObject (Java Platform SE 7)" . docs.oracle.com . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ↑ مايكروسوفت ليرن (11 مارس 2026). "إرشادات حقن التبعية" . learn.microsoft.com . مايكروسوفت ليرن.
- ↑ ريل، ديرك (2000)، تصميم الأطر: نهج نمذجة الأدوار (ملف PDF) ، المعهد الفدرالي السويسري للتكنولوجيا
- ↑ "حقن التبعية ≠ استخدام حاوية حقن التبعية" . www.loosecouplings.com . تم الاطلاع عليه بتاريخ 18 يوليو 2015 .
- ↑ "بلاك شيب » اصنعها بنفسك » طباعة" . blacksheep.parry.org . مؤرشف من الأصل بتاريخ 27-06-2015 . تم الاطلاع عليه بتاريخ 18-07-2015 .
- ^ جاكرتا إي إي (16 أكتوبر 2021). "الحزمة jakarta.inject" . jakarta.ee . جاكرتا إي.
- ↑ مايكروسوفت ليرن. "مساحة اسم حقن التبعية في Microsoft.Extensions" . learn.microsoft.com . مايكروسوفت ليرن . تم الاطلاع عليه في 6 يوليو 2026 .
- ↑ "نصائح الربيع: كائن POJO مع التعليقات ليس كائنًا عاديًا" . مؤرشف من الأصل بتاريخ 15 يوليو 2015. تم الاطلاع عليه بتاريخ 18 يوليو 2015 .
- ↑ "التعليقات التوضيحية في POJO - نعمة أم نقمة؟ | Techtracer" . 2007-04-07 . تم الاطلاع عليه بتاريخ 2015-07-18 .
- ↑ وحدات Pro Spring الديناميكية لمنصات خدمة OSGi . APress. 17-02-2009. ISBN 9781430216124تم الاطلاع عليه بتاريخ 2015-07-06 .
- ↑ كريس جوسياك (2 أبريل 2025). " [ Boost::ext ] .DI" . boost-ext.github.io . مكتبات Boost C++.
- ↑ آدم لاش، والتر جينوفيز (15 مارس 2025). "انعكاس معلمات الدوال في الانعكاس لـ C++26" (ملف PDF) . open-std.org . WG 21.
- ↑ وايت تشيلدرز، دان كاتز، باري ريزفين، دافيد فانديفورد (19 يونيو 2025). "شروح للتأمل" . isocpp.org . WG21.
{{cite web}}: صيانة CS1: أسماء متعددة: قائمة المؤلفين ( رابط ) - ↑ "ملاحظات إصدار Go 1.18 - لغة برمجة Go" . go.dev . تم الاطلاع عليه بتاريخ 17-04-2024 .
- ↑ "Awesome Go – حقن التبعية" . جيت هاب . ١٧ أبريل ٢٠٢٤. تم الاطلاع عليه في ١٧ أبريل ٢٠٢٤ .
روابط خارجية
- تكوين الجذر من قبل مارك سيمان
- دليل المبتدئين لتقنية حقن التبعية
- حقن التبعية والكائنات القابلة للاختبار: تصميم كائنات مترابطة بشكل فضفاض وقابلة للاختبار - جيريمي وايسكوتين؛ مجلة دكتور دوب ، مايو 2006.
- أنماط التصميم: حقن التبعية - مجلة MSDN، سبتمبر 2005
- المقال الأصلي لمارتن فاولر الذي قدم مصطلح حقن التبعية
- P من EAA: إضافة
- التراث الهندسي الغني وراء حقن التبعية - أندرو مكفي - تاريخ مفصل لحقن التبعية.
- ما هو حقن التبعية؟ - شرح بديل - جاكوب جينكوف
- كتابة كود أكثر قابلية للاختبار باستخدام حقن التبعية -- Developer.com، أكتوبر 2006. مؤرشف في 11 مارس 2008 على موقع Wayback Machine.
- نظرة عامة على إطار عمل قابلية التوسعة المُدارة -- MSDN
- وصف قديم لآلية التبعية من قبل هانت 1998
- أعد هيكلة طريقك إلى حاوية حقن التبعية
- فهم حقن التبعية في لغة PHP
- لست بحاجة إلى حاوية حقن التبعية
- هندسة البرمجيات القائمة على المكونات
- هندسة البرمجيات
- أنماط تصميم البرمجيات
