Рубрики
Без рубрики

Учебное пособие по внедрению зависимостей Java – Пример шаблона проектирования DI

Внедрение зависимостей в Java – Что такое шаблон проектирования внедрения зависимостей в Java. Пример руководства по внедрению зависимостей Java, преимущества или недостатки и недостатки

Автор оригинала: Pankaj Kumar.

Внедрение зависимостей Java шаблон проектирования позволяет нам удалить жестко запрограммированные зависимости и сделать наше приложение слабо связанным, расширяемым и ремонтопригодным. Мы можем реализовать внедрение зависимостей в java , чтобы перенести разрешение зависимостей из времени компиляции во время выполнения.

Внедрение Зависимостей Java

Внедрение зависимостей Java кажется трудным для понимания в теории, поэтому я бы взял простой пример, а затем мы увидим, как использовать шаблон внедрения зависимостей для достижения слабой связи и расширяемости в приложении.

Допустим, у нас есть приложение, в котором мы используем Службу электронной почты для отправки электронных писем. Обычно мы бы реализовали это, как показано ниже.

package com.journaldev.java.legacy;

public class EmailService {

	public void sendEmail(String message, String receiver){
		//logic to send email
		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){
		//do some msg validation, manipulation logic etc
		this.email.sendEmail(msg, rec);
	}
}

Наш клиентский код, который будет использовать Мое приложение класс для отправки сообщений электронной почты, будет выглядеть следующим образом.

package com.journaldev.java.legacy;

public class MyLegacyTest {

	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("Hi Pankaj", "pankaj@abc.com");
	}

}

На первый взгляд, кажется, что в приведенной выше реализации нет ничего плохого. Но вышеприведенная логика кода имеет определенные ограничения.

  • Мое приложение класс отвечает за инициализацию службы электронной почты, а затем за ее использование. Это приводит к жестко заданной зависимости. Если в будущем мы захотим переключиться на какую-либо другую продвинутую службу электронной почты, для этого потребуются изменения кода в моем классе приложений. Это затрудняет расширение нашего приложения, и если служба электронной почты используется в нескольких классах, то это будет еще сложнее.
  • Если мы хотим расширить наше приложение, чтобы предоставить дополнительную функцию обмена сообщениями, такую как SMS или сообщение на Facebook, нам нужно будет написать для этого другое приложение. Это повлечет за собой изменения кода в классах приложений, а также в клиентских классах.
  • Тестирование приложения будет очень сложным, так как наше приложение непосредственно создает экземпляр службы электронной почты. Мы никак не можем издеваться над этими объектами в наших тестовых классах.

Можно утверждать, что мы можем удалить создание экземпляра службы электронной почты из Моего приложения класса, используя конструктор, для которого в качестве аргумента требуется служба электронной почты.

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){
		//do some msg validation, manipulation logic etc
		this.email.sendEmail(msg, rec);
	}
}

Но в этом случае мы просим клиентские приложения или тестовые классы инициализировать службу электронной почты, что не является хорошим дизайнерским решением.

Теперь давайте посмотрим, как мы можем применить шаблон внедрения зависимостей java для решения всех проблем, связанных с вышеуказанной реализацией. Внедрение зависимостей в java требует, по крайней мере, следующего:

  1. Сервисные компоненты должны быть разработаны с использованием базового класса или интерфейса. Лучше предпочесть интерфейсы или абстрактные классы, которые определяли бы контракт на услуги.
  2. Классы потребителей должны быть написаны в терминах интерфейса сервиса.
  3. Классы инжекторов, которые будут инициализировать службы, а затем классы потребителей.

Внедрение Зависимостей Java – Сервисные Компоненты

В нашем случае у нас может быть Служба сообщений , которая объявит контракт на реализацию услуг.

package com.journaldev.java.dependencyinjection.service;

public interface MessageService {

	void sendMessage(String msg, String rec);
}

Теперь предположим, что у нас есть службы электронной почты и SMS, которые реализуют вышеуказанные интерфейсы.

package com.journaldev.java.dependencyinjection.service;

public class EmailServiceImpl implements MessageService {

	@Override
	public void sendMessage(String msg, String rec) {
		//logic to send email
		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) {
		//logic to send SMS
		System.out.println("SMS sent to "+rec+ " with Message="+msg);
	}

}

Наши java-сервисы для внедрения зависимостей готовы, и теперь мы можем написать наш потребительский класс.

Внедрение Зависимостей Java – Потребитель Услуг

Нам не требуется иметь базовые интерфейсы для потребительских классов, но у меня будет Потребитель интерфейс, объявляющий контракт для потребительских классов.

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){
		//do some msg validation, manipulation logic etc
		this.service.sendMessage(msg, rec);
	}

}

Обратите внимание, что наш класс приложений просто использует сервис. Он не инициализирует службу, что приводит к лучшему ” разделению интересов “. Кроме того, использование интерфейса службы позволяет нам легко тестировать приложение, издеваясь над службой сообщений и связывая службы во время выполнения, а не во время компиляции.

Теперь мы готовы написать классы внедрения зависимостей java , которые будут инициализировать службу, а также классы потребителей.

Инъекция Зависимостей Java – Классы Инжекторов

Давайте создадим интерфейс Инжектор службы сообщений с объявлением метода, который возвращает класс Потребитель .

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 = "pankaj@abc.com";
		String phone = "4088888888";
		MessageServiceInjector injector = null;
		Consumer app = null;
		
		//Send email
		injector = new EmailServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, email);
		
		//Send SMS
		injector = new SMSServiceInjector();
		app = injector.getConsumer();
		app.processMessages(msg, phone);
	}

}

Как вы можете видеть, наши классы приложений отвечают только за использование сервиса. Классы обслуживания создаются в инжекторах. Кроме того, если нам придется еще больше расширить наше приложение, чтобы разрешить обмен сообщениями в Facebook, нам придется писать только классы обслуживания и классы инжекторов.

Таким образом, внедрение внедрения зависимостей решило проблему с жестко запрограммированной зависимостью и помогло нам сделать наше приложение гибким и простым в расширении. Теперь давайте посмотрим, как легко мы можем протестировать наш класс приложений, издеваясь над классами инжектора и сервиса.

Инъекция зависимостей Java – Тестовый случай 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(){
		//mock the injector with anonymous class
		injector = new MessageServiceInjector() {
			
			@Override
			public Consumer getConsumer() {
				//mock the message service
				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", "pankaj@abc.com");
	}
	
	@After
	public void tear(){
		injector = null;
	}

}

Как вы можете видеть, я использую анонимные классы для моделирования инжектора и классов обслуживания и я могу легко протестировать свои методы применения. Я использую JUnit 4 для вышеуказанного тестового класса, поэтому убедитесь, что он указан в пути сборки вашего проекта, если вы работаете над тестовым классом.

Мы использовали конструкторы для внедрения зависимостей в классы приложений, другой способ-использовать метод setter для внедрения зависимостей в классы приложений. Для внедрения зависимостей метода setter наш класс приложений будет реализован, как показано ниже.

package com.journaldev.java.dependencyinjection.consumer;

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

public class MyDIApplication implements Consumer{

	private MessageService service;
	
	public MyDIApplication(){}

	//setter dependency injection	
	public void setService(MessageService service) {
		this.service = service;
	}

	@Override
	public void processMessages(String msg, String rec){
		//do some msg validation, manipulation logic etc
		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;
	}

}

Одним из лучших примеров внедрения зависимостей сеттера являются интерфейсы, поддерживающие API сервлетов Struts2 .

Использовать ли внедрение зависимостей на основе конструктора или на основе сеттера-это проектное решение и зависит от ваших требований. Например, если мое приложение вообще не может работать без класса обслуживания, я бы предпочел DI на основе конструктора, иначе я бы выбрал DI на основе метода настройки, чтобы использовать его только тогда, когда это действительно необходимо.

Внедрение зависимостей в Java – это способ достижения Инверсии управления ( IoC ) в нашем приложении путем перемещения привязки объектов из времени компиляции во время выполнения. Мы можем достичь IoC с помощью Заводского шаблона , Шаблона проектирования метода шаблона , Шаблона стратегии и шаблона локатора служб.

Внедрение зависимостей Spring , Google Guice и Java EE CDI фреймворки облегчают процесс внедрения зависимостей за счет использования API отражения Java и аннотаций java . Все, что нам нужно, – это аннотировать поле, конструктор или метод настройки и настроить их в конфигурационных xml-файлах или классах.

Преимущества внедрения зависимостей Java

Некоторые из преимуществ использования внедрения зависимостей в Java заключаются в следующем:

  • Разделение интересов
  • Сокращение стандартного кода в классах приложений, поскольку вся работа по инициализации зависимостей выполняется компонентом инжектора
  • Настраиваемые компоненты делают приложение легко расширяемым
  • Модульное тестирование легко с помощью макетных объектов

Недостатки внедрения зависимостей Java

Внедрение зависимостей Java также имеет некоторые недостатки:

  • При чрезмерном использовании это может привести к проблемам с обслуживанием, поскольку последствия изменений известны во время выполнения.
  • Внедрение зависимостей в java скрывает зависимости класса обслуживания, которые могут привести к ошибкам во время выполнения, которые были бы обнаружены во время компиляции.

Это все для шаблона внедрения зависимостей в java . Это полезно знать и использовать, когда мы контролируем услуги.