حقن التبعية في جافا – مثال على نمط التصميم DI

نمط تصميم حقن الاعتماد في جافا يسمح لنا بإزالة الاعتمادات المُدمجة وجعل تطبيقنا مرتبطًا بصورة فضفاضة وقابل للتوسع وسهل الصيانة. يمكننا تنفيذ حقن الاعتماد في جافا لنقل عملية حل الاعتماد من وقت الترجمة إلى وقت التنفيذ.

حقن الاعتماد في جافا

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

package com.journaldev.java.legacy;

public class EmailService {

	public void sendEmail(String message, String receiver){
		//منطق لإرسال البريد الإلكتروني
		System.out.println("Email sent to "+receiver+ " with Message="+message);
	}
}

يحتوي الفئة EmailService على منطق لإرسال رسالة بريد إلكتروني إلى عنوان البريد الإلكتروني المستلم. سيكون كود التطبيق لدينا على النحو التالي.

package com.journaldev.java.legacy;

public class MyApplication {

	private EmailService email = new EmailService();
	
	public void processMessages(String msg, String rec){
		//قم بتنفيذ بعض منطق التحقق من الرسالة وتلاعب فيها الخ
		this.email.sendEmail(msg, rec);
	}
}

سيكون كود العميل الذي سيستخدم الفئة MyApplication لإرسال رسائل البريد الإلكتروني على النحو التالي.

package com.journaldev.java.legacy;

public class MyLegacyTest {

	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("Hi Pankaj", "[email protected]");
	}

}

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

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

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

package com.journaldev.java.legacy;

public class MyApplication {

	private EmailService email = null;
	
	public MyApplication(EmailService svc){
		this.email=svc;
	}
	
	public void processMessages(String msg, String rec){
		//قم بتنفيذ بعض منطق التحقق والتلاعب في الرسائل الخ
		this.email.sendEmail(msg, rec);
	}
}

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

  1. يجب تصميم مكونات الخدمة بفئة أساسية أو واجهة. من الأفضل أن تفضل الواجهات أو الفئات المجردة التي تحدد العقدة للخدمات.
  2. يجب كتابة فئات المستهلك بتحديد واجهة الخدمة.
  3. فئات الحاقن التي ستقوم بتهيئة الخدمات ومن ثم فئات المستهلك.

حقن الاعتمادية في جافا – مكونات الخدمة

بالنسبة لحالتنا، يمكننا أن نمتلك MessageService التي ستعلن عقد تنفيذات الخدمة.

package com.journaldev.java.dependencyinjection.service;

public interface MessageService {

	void sendMessage(String msg, String rec);
}

الآن لنفترض أن لدينا خدمات البريد الإلكتروني والرسائل القصيرة التي تنفذ الواجهات المذكورة أعلاه.

package com.journaldev.java.dependencyinjection.service;

public class EmailServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		//منطق إرسال البريد الإلكتروني 
		System.out.println("Email sent to "+rec+ " with Message="+msg);
	}

}
package com.journaldev.java.dependencyinjection.service;

public class SMSServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		//منطق إرسال الرسائل القصيرة 
		System.out.println("SMS sent to "+rec+ " with Message="+msg);
	}

}

خدمات الحقن الخاصة بنا في جافا جاهزة والآن يمكننا كتابة فئتنا المستهلكة.

حقن الاعتمادية في جافا – مستهلك الخدمة

لسنا مطالبين بأن يكون لدينا واجهات أساسية لفئات المستهلك ولكن سأمتلك واجهة Consumer التي تعلن عقد المستهلك فئات.

package com.journaldev.java.dependencyinjection.consumer;

public interface Consumer {

	void processMessages(String msg, String rec);
}

تنفيذ فئة المستهلك الخاصة بي هو كما يلي.

package com.journaldev.java.dependencyinjection.consumer;

import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(MessageService svc){
		this.service=svc;
	}
	
	@Override
	public void processMessages(String msg, String rec){
		//قم ببعض التحقق من صحة الرسالة، ومنطق التلاعب بالرسالة الخ.
		this.service.sendMessage(msg, rec);
	}

}

لاحظ أن فئة التطبيق الخاصة بنا تستخدم فقط الخدمة. إنها لا تقوم بتهيئة الخدمة مما يؤدي إلى “فصل المسؤوليات” الأفضل. كما أن استخدام واجهة الخدمة يتيح لنا اختبار التطبيق بسهولة عن طريق تجاهل خدمة الرسائل وربط الخدمات في وقت التشغيل بدلاً من وقت الترجمة. الآن نحن جاهزون لكتابة “فئات حاقن الاعتمادية جافا” التي ستهيئ الخدمة وكذلك فئات المستهلك.

حقن الاعتمادية في جافا – فئات الحاقن

لنقم بإنشاء واجهة MessageServiceInjector مع إعلان الطريقة التي تعيد فئة Consumer.

package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;

public interface MessageServiceInjector {

	public Consumer getConsumer();
}

الآن، لكل خدمة، سنحتاج إلى إنشاء فئات حاقن كما يلي.

package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new EmailServiceImpl());
	}

}
package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;

public class SMSServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new SMSServiceImpl());
	}

}

الآن، دعنا نرى كيف ستستخدم تطبيقات العميل التطبيق مع برنامج بسيط.

package com.journaldev.java.dependencyinjection.test;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;

public class MyMessageDITest {

	public static void main(String[] args) {
		String msg = "Hi Pankaj";
		String email = "[email protected]";
		String phone = "4088888888";
		MessageServiceInjector injector = null;
		Consumer app = null;
		
		// إرسال بريد إلكتروني 
		injector = new EmailServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, email);
		
		// إرسال رسالة نصية 
		injector = new SMSServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, phone);
	}

}

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

حقن التبعية في جافا – حالة اختبار JUnit مع محاكي الحاقن والخدمة المزيفة

package com.journaldev.java.dependencyinjection.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplicationJUnitTest {

	private MessageServiceInjector injector;
	@Before
	public void setUp(){
		// محاكاة الحاقن باستخدام فئة مجهولة
		injector = new MessageServiceInjector() {
			
			@Override
			public Consumer getConsumer() {
				// محاكاة خدمة الرسائل
				return new MyDIApplication(new MessageService() {
					
					@Override
					public void sendMessage(String msg, String rec) {
						System.out.println("Mock Message Service implementation");
						
					}
				});
			}
		};
	}
	
	@Test
	public void test() {
		Consumer consumer = injector.getConsumer();
		consumer.processMessages("Hi Pankaj", "[email protected]");
	}
	
	@After
	public void tear(){
		injector = null;
	}

}

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

package com.journaldev.java.dependencyinjection.consumer;

import com.journaldev.java.dependencyinjection.service.MessageService;

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(){}

	// حقن التبعية باستخدام طريقة الوضع	
	public void setService(MessageService service) {
		this.service = service;
	}

	@Override
	public void processMessages(String msg, String rec){
		// قم بتنفيذ بعض المنطق للتحقق من الرسالة وتلاعب بها وما إلى ذلك
		this.service.sendMessage(msg, rec);
	}

}
package com.journaldev.java.dependencyinjection.injector;

import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		MyDIApplication app = new MyDIApplication();
		app.setService(new EmailServiceImpl());
		return app;
	}

}

واحدة من أفضل أمثلة حقن التبعية للمعين هي واجهات Struts2 Servlet API Aware. سواء كان استخدام حقن التبعية بناءً على البناء أم بناءً على طريقة الضبط فهو قرار تصميمي ويعتمد على متطلباتك. على سبيل المثال، إذا كان التطبيق الخاص بي لا يمكن أن يعمل على الإطلاق بدون فئة الخدمة، فسأفضل حقن التبعية بناءً على البناء. وإلا، سألجأ إلى حقن التبعية بناءً على طريقة الضبط لاستخدامها فقط عند الحاجة الفعلية لها. حقن التبعية في جافا هو وسيلة لتحقيق عكس التحكم (IoC) في تطبيقنا من خلال نقل ربط الكائنات من وقت الترجمة إلى وقت التشغيل. يمكننا تحقيق IoC من خلال نمط المصنع، نمط التصميم للطريقة القالبية، نمط الاستراتيجية ونمط محدد الموقع للخدمة. تسهل إطارات حقن التبعية في Spring، Google Guice و Java EE CDI عملية حقن التبعية من خلال استخدام واجهة برمجة التطبيقات الانعكاسية في جافا و التعليقات في جافا. كل ما نحتاجه هو تعليق الحقل أو البناء أو طريقة الضبط وتكوينها في ملفات تكوين XML أو فئات.

فوائد حقن التبعية في جافا

بعض فوائد استخدام حقن التبعية في جافا هي:

  • فصل الاهتمامات
  • تقليل الشفرة المكررة في فئات التطبيق لأن جميع العمل لتهيئة التبعيات يتم من خلال مكون المحقن
  • إمكانية تكوين المكونات تجعل التطبيق قابلاً للتمديد بسهولة
  • اختبار الوحدات سهل مع كائنات الاختبار المزيفة

عيوب حقن التبعية في جافا

حقن التبعية في جافا لديه بعض العيوب أيضًا:

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

تنزيل مشروع حقن التبعية

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

Source:
https://www.digitalocean.com/community/tutorials/java-dependency-injection-design-pattern-example-tutorial