JavaRush /Java блог /Random UA /Простий спосіб застосування залежностей

Простий спосіб застосування залежностей

Стаття з групи Random UA
Впровадження залежностей або ін'єкція залежностей (Dependency injection, DI) – непроста для розуміння концепція, а її застосування до нових або вже існуючих програм – завдання ще більш заплутане. Джесс Сміт покаже вам, як здійснювати використання залежностей без контейнера застосування мовами програмування C# і Java. Простий спосіб застосування залежностей - 1У цій статті я покажу вам, як впроваджувати залежності (DI) у .NET-і Java-додатках. Концепція впровадження залежностей вперше з'явилася в полі зору розробників у 2000 році, коли Роберт Мартін написав статтю "Принципи та патерни проектування" (пізніше здобули популярність під абревіатурою SOLID). Літера D у SOLID відноситься до інверсії залежностей (Dependency of Inversion, DOI), яку пізніше стали називати впровадженням залежностей. Початкове і найчастіше визначення: інверсія залежностей — це інверсія способу управління залежностями базовим класом. У вихідній статті Мартіна використовувався наступний код, що ілюструє залежність класу Copyвід низькорівневого класу WritePrinter:
void Copy()
	{
	 int c;
	 while ((c = ReadKeyboard()) != EOF)
		WritePrinter(c);
	}
Перша очевидна проблема: якщо змінити список або типи параметрів методу WritePrinter, потрібно впровадити поновлення скрізь, де є залежність від цього методу. Цей процес підвищує витрати на обслуговування та є потенційним джерелом нових помилок.
Цікаво читати про Java? Вступайте до групи Java Developer !
Інша проблема: клас Copy перестає бути потенційним кандидатом повторне використання. Наприклад, що робити, якщо вам потрібно вивести символи, що вводяться з клавіатури, у файл замість принтера? Для цього можна модифікувати клас Copyу такий спосіб (синтаксис мови C++):
void Copy(outputDevice dev)
	{
	int c;
	while ((c = ReadKeyboard()) != EOF)
		if (dev == printer)
			WritePrinter(c);
		else
			WriteDisk(c);
	}
Незважаючи на появу нової залежності WriteDisk, ситуація не покращилася (а скоріше погіршилася), оскільки було порушено інший принцип: "програмні сутності, тобто класи, модулі, функції і так далі, повинні бути відкриті для розширення, але закриті для зміни". Мартін пояснює, що ці нові умовні оператори if/else знижують стабільність та гнучкість коду. Рішення полягає в інверсії залежностей, щоб методи запису та читання залежали від класу Copy. Замість "виштовхування" залежностей вони передаються через конструктор. Перероблений код виглядає так:
class Reader
	{
		public:
		virtual int Read() = 0;
	};
	class Writer
	{
		public:
		virtual void Write(char) = 0;
	};
	void Copy(Reader& r, Writer& w)
	{
		int c;
		while((c=r.Read()) != EOF)
		w.Write(c);
	}
Тепер клас Copyможна легко використовувати повторно з різними реалізаціями методів класів Readerта Writer. У класу Copyнемає жодної інформації про внутрішній устрій типів Readerі Writerзавдяки чому можливе їх перевикористання з різними реалізаціями. Але якщо все це здається вам якоюсь абракадаброю, можливо, ситуацію прояснять наведені нижче приклади мовами Java та C#.

Приклад мовами Java і C#

Для ілюстрації простоти застосування залежностей без контейнера залежностей, почнемо з простого прикладу, який можна переробити під використання DIвсього за кілька кроків. Припустимо, у нас є клас HtmlUserPresentation, який, при виклику його методів, формує HTML-інтерфейс. Ось простий приклад:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
У будь-якого класу проекту, що використовує цей код, з'являється залежність від класу HtmlUserPresentation, що призводить до вищеописаних проблем зі зручністю використання та обслуговуванням. Відразу напрошується вдосконалення: створення інтерфейсу з сигнатурами всіх HtmlUserPresentationметодів, що нині є в класі. Ось приклад цього інтерфейсу:
public interface IHtmlUserPresentation {
	String createTable(ArrayList rowVals, String caption);
	String createTableRow(String tableCol);
	// Оставшиеся сигнатуры
}
Після створення інтерфейсу модифікуємо клас HtmlUserPresentationдля його використання. Повертаючись до створення екземпляра типу HtmlUserPresentation, ми можемо використовувати тип інтерфейсу замість базового:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Створення інтерфейсу дозволяє легко використовувати інші реалізації типу IHtmlUserPresentation. Наприклад, якщо ми хочемо протестувати цей тип, то легко можемо замінити базовий тип HtmlUserPresentationіншим типом, під назвою HtmlUserPresentationTest. Виконані досі зміни спрощують тестування, обслуговування та масштабування коду, але нічого не роблять для перевикористання, оскільки всі HtmlUserPresentationкласи, що використовують тип, все ще знають про його існування. Щоб прибрати цю пряму залежність, можна передавати інтерфейсний тип IHtmlUserPresentationконструктор (або список параметрів методу) класу або метод, який його використовуватиме:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
Конструктор UploadFileтепер має доступ до всієї функціональності типу IHtmlUserPresentation, але він нічого не знає про внутрішній пристрій реалізує цей інтерфейс класу. У цьому контексті, використання типу відбувається при створенні екземпляра класу UploadFile. Інтерфейсний тип IHtmlUserPresentationстає перевикористовуваним, передаючи різні реалізації різним класам чи методам, котрим необхідна різна функціональність.

Висновок та рекомендації для закріплення матеріалу

Ви дізналися про те, що таке використання залежностей і про те, що класи називаються безпосередньо залежними один від одного тоді, коли один з них створює екземпляр іншого для отримання доступу до функціональності цільового типу. Для розчеплення прямої залежності між двома типами слід створити інтерфейс. Інтерфейс надає типу можливість включати різні реалізації залежно від контексту необхідної функціональності. Завдяки передачі інтерфейсного типу конструктору або методу класу, клас/метод, для якого потрібна функціональність, не знає ніяких подробиць про тип, що реалізує інтерфейс. Через це інтерфейсний тип можна використовувати повторно для різних класів, що вимагають схожого, але з однакового поведінки.
  • Щоб поекспериментувати з впровадженням залежностей, перегляньте свій код з одного або декількох додатків і спробуйте переробити базовий тип, що інтенсивно використовується, в інтерфейс.

  • Змініть класи, що безпосередньо створюють екземпляри цього базового типу, так, щоб вони використовували цей новий інтерфейсний тип і передавали його через конструктор або список параметрів методу класу, який його повинен буде використовувати.

  • Створіть тестову реалізацію для перевірки цього типу інтерфейсу. Після рефакторингу вашого коду реалізувати DIстане простіше, і ви помітите, наскільки гнучкішим стане ваш додаток у сенсі перевикористання та супроводу.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ