Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Знакомимся с Dependency Injection на примере Dagger

Anna | 3.06.2014 | нет комментариев
В данной статье мы попытаемся разобраться с Dependency Injection в Android (и не только) на примере набирающей знаменитость open source библиотеки Dagger
И так, что же такое Dependency Injection? Согласно википедии, это design pattern, разрешающий динамически описывать зависимости в коде, разделяя бизнес-логику на больше мелкие блоки. Это комфортно в первую очередь тем, что позднее дозволено эти самые блоки подменять тестовыми, тем самым ограничивая зону тестирования.

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

Разглядим упрощенную (до псевдокода) версию Twitter заказчика.

В теории, диаграмма зависимостей выглядит приблизительно так:

Давайте взглянем как это выглядит в коде:

public class Tweeter
{
	public void tweet(String tweet)
	{
		TwitterApi api = new TwitterApi();
		api.postTweet("Test User", tweet);
	}
}

public class TwitterApi
{
	public void postTweet(String user, String tweet)
	{
		HttpClient client = new OkHttpClient();
		HttpUrlConnection connection = client.open("....");
		/* post tweet */
	}
}

Как видим, комплект интерфейсов достаточно примитивен, следственно применять мы это будем приблизительно так:

Tweeter tweeter = new Tweeter();
tweeter.tweet("Hello world!");

Пока все идет отлично, твиты улетают — все радостны. Сейчас появляется надобность все это протестировать. Сразу же примечаем, что недурно было бы иметь вероятность подменять Http заказчик на тестовый, Дабы возвращать какой-нибудь тестовый итог и не ломиться в сеть всякий раз. В этом случае, нам нужно снять с класса TwitterApi обязанность создавать Http заказчик и сгрузить эту обязанность вышестоящим классам. Наш код немножко преображается:

public class Tweeter
{
	private TwitterApi api;
	public Tweeter(HttpClient httpClient)
	{
		this.api = new TwitterApi(httpClient);
	}

	public void tweet(String tweet)
	{
		api.postTweet("Test User", tweet);
	}
}

public class TwitterApi
{
	private HttpClient client;

	public TwitterApi(HttpClient client)
	{
		this.client = client;
	}

	public void postTweet(String user, String tweet)
	{
		HttpUrlConnection connection = client.open("....");
		/* post tweet */
	}
}

Сейчас мы видим, что при необходимости простестировать наш код, мы можем легко «подставить» тестовый Http заказчик, тот, что будет возвращать тестовые итоги:

Tweeter tweeter = new Tweeter(new MockHttpClient);
tweeter.tweet("Hello world!");

Казалось бы, что может быть проще? На самом деле, теперь мы «вручную» реализовали Dependency Injection паттерн. Но есть одно «но». Предположим обстановку, что у нас есть класс Timeline, тот, что может загружать последние n сообщений. Данный класс тоже использует TwitterApi:

Timeline timeline = new Timeline(new OkHttpClient(), "Test User");
timeline.loadMore(20);
for(Tweet tweet: timeline.get())
{
	System.out.println(tweet);
}

Наш класс выглядит приблизительно так:

public class Timeline
{
	String user;
	TweeterApi api;
	public Timeline(HttpClient httpClient, String user)
	{
		this.user = user;
		this.api = new TweeterApi(httpClient);
	}

	public void loadMore(int n){/*.....*/}
	public List<Tweet> get(){/*.......*/}
}

Как бы бы все ничего — мы применили тот же подход, что и с классом Tweeter — дали вероятность указывать Http заказчик при создании объекта, что разрешает нам протестировать данный модуль, не завися при этом от сети. Но! Вы подметили, сколько кода мы продублировали и как нам доводится «протаскивать» Http заказчик прямо из «головы» приложения? Безусловно, дозволено добавить конструкторы по умолчанию, которые будут создавать настоящий Http заказчик, и применять кастомный конструктор только при тестировании, но чай это не решает задачу, а только маскирует ее.

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

Dagger

Dagger — это open source Dependency Injection библиотека от разработчиков okhttp, retrofit, picasso и многих других восхитительных библиотек, знаменитых многим Android разработчикам.

Основные превосходства Dagger (по сопоставлению с тем же Guice):

  • Статический обзор всех зависимостей
  • Определение ошибок конфигурации на этапе компиляции (не только в runtime)
  • Отсутсвие reflection, что гораздо ускоряет процесс конфигурации
  • Достаточно маленькая нагрузка на память

В Dagger процесс конфигурации зависимостей разбит на 3 крупных блока:

  • инициализация графа завизимостей (ObjectGraph)
  • запрос зависимостей (@Inject)
  • удовлетворение зависимостей (@Module/@Provides)
Запрос зависимостей (request dependency)

Дабы попросить Dagger проиницализировать одно из полей, все что необходимо сделать — добавить аннотацию @Inject:

@Inject
private HttpClient client;

… и удостовериться, что данный класс добавлен в граф зависимостей (об этом дальше)

Удовлетворение зависимостей (provide dependency)

Дабы сказать даггеру какой инстанс заказчика нужно сделать, нужно сделать «модуль» — класс аннотированный @Module:

@Module
public class NetworkModule{...}

Данный класс отвечает за «удовлетворение» части зависимостей, запрошенных приложением. В этом классе необходимо сделать так называемый «провайдер» — способ, тот, что возвращает инстанс HttpClient (аннотированный @Provide):

@Module(injects=TwitterApi.class)
public class NetworkModule
{
	@Provides @Singleton
	HttpClient provideHttpClient()
	{
		return new OkHttpClient();
	}
}

Этим мы сказали Dagger’y, Дабы он сотворил OkHttpClient для всякого, кто попросил HttpClient посредством@Inject аннотации

Стоит упомянуть, что для того, Дабы compile-time валидация работала, нужно указать все классы (в параметре injects), которые просят эту связанность. В нашем случае, HttpClient нужен только TwitterApi классу.
Аннотация @Singleton указывает Dagger’у, что нужно сделать только 1 инстанс заказчика и закэшировать его.

Cоздание графа

Сейчас перейдем к созданию графа. Для этого я сотворил класс Injector, тот, что инициализирует граф одним (либо больше) модулем. В контексте Android приложения, комфортней каждого это делать при создании приложения (наследуемся от Application и перегружаем onCreate()). В данном примере, я сотворил TweeterApp клас, тот, что содержит в себе остальные компоненты (Tweeter и Timeline)

public class Injector
{
	public static ObjectGraph graph;
	public static void init(Object... modules)
	{
		graph = ObjectGraph.create(modules);
	}

	public static void inject(Object target)
	{
		graph.inject(target);
	}
}

public class TweeterApp
{
	public static void main(String... args)
	{
		Injector.init(new NetworkModule());
		Tweeter tweeter = new Tweeter();
		tweeter.tweet("Hello world");
		Timeline timeline = new Timeline("Test User");
		timeline.loadMore(20);
		for(Tweet tweet: timeline.get())
		{
			System.out.println(tweet);
		}
	}
}

Сейчас возвратимся к запросу зависимостей:

public class TwitterApi
{
	@Inject
	private HttpClient client;

	public TwitterApi()
	{
        //Добавляем класс в граф зависимостей
		Injector.inject(this);
        //На этом этапе "магическим" образом client проинициализирован Dagger'ом
	}

	public void postTweet(String user, String tweet)
	{
		HttpUrlConnection connection = client.open("....");
		/* post tweet */
	}
}

Подметьте Injector.inject(Object). Это нужно для того, Дабы добавить класс в граф зависимостей. Т.е. если у нk!>Мой небольшой пример применения библиотеки на GitHub

 

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB