В то время как инъекция зависимостей (aka, «DI») - естественный метод создания объектов в ООП (известный задолго до того, как термин был введен Мартином Фаулером), Spring IoC, Google Guice, Java EE6 CDI, Dagger и другие структуры DI превращают его в Анти-шаблон.
Я не собираюсь обсуждать очевидные аргументы против «инъекций сеттера» (например, Spring IoC) и «инъекции полей» (как в PicoContainer). Эти механизмы просто нарушают основные принципы объектно-ориентированного программирования и побуждают нас создавать неполные, изменяемые объекты, которые заполняются данными в ходе выполнения приложения. Помните: идеальные объекты должны быть неизменными и не должны содержать сеттеров.
Вместо этого давайте поговорим о «введении конструктора» (как в Google Guice) и его использовании с контейнерами инъекции зависимостей. Я попытаюсь показать, почему я рассматриваю эти контейнеры как избыточность.
Я не собираюсь обсуждать очевидные аргументы против «инъекций сеттера» (например, Spring IoC) и «инъекции полей» (как в PicoContainer). Эти механизмы просто нарушают основные принципы объектно-ориентированного программирования и побуждают нас создавать неполные, изменяемые объекты, которые заполняются данными в ходе выполнения приложения. Помните: идеальные объекты должны быть неизменными и не должны содержать сеттеров.
Вместо этого давайте поговорим о «введении конструктора» (как в Google Guice) и его использовании с контейнерами инъекции зависимостей. Я попытаюсь показать, почему я рассматриваю эти контейнеры как избыточность.
Что такое инъекция зависимостей?
Это то, что представляет собой инъекция зависимостей (на самом деле не отличается от простой старой композиции объекта):
public class Budget {
private final DB db;
public Budget(DB data) {
this.db = data;
}
public long total() {
return this.db.cell(
"SELECT SUM(cost) FROM ledger"
);
}
}
Данные объекта называются «зависимость».
Бюджет не знает, с какой базой данных он работает. Все, что ему нужно от базы данных - это возможность получить ячейку, используя произвольный SQL-запрос, с помощью метода cell (). Мы можем создать экземпляр бюджета с использованием PostgreSQL интерфейса DB, например:
public class App {
public static void main(String... args) {
Budget budget = new Budget(
new Postgres("jdbc:postgresql:5740/main")
);
System.out.println("Total is: " + budget.total());
}
}
Другими словами, мы «вводим» зависимость в новый бюджет объекта.
Альтернативой этому «инъекционному подходу» было бы позволить Бюджету решить, с какой базой данных он хочет работать:
public class Budget {
private final DB db =
new Postgres("jdbc:postgresql:5740/main");
// class methods
}
Это очень грязно и приводит к 1) дублированию кода, 2) невозможности повторного использования, 3) невозможности тестирования и т. Д. Не нужно обсуждать почему. Это очевидно.
Таким образом, вставка зависимостей через конструктор является удивительной техникой. Ну, вообще-то, даже не техника. Это больше похоже на Java и все другие объектно-ориентированные языки. Ожидается, что почти любой объект захочет инкапсулировать некоторые знания (иначе, «состояние»). Для этого и нужны конструкторы.
Что такое контейнер DI?
Пока все хорошо, но здесь идет темная сторона - контейнер для инъекций зависимостей. Вот как это работает (давайте использовать Google Guice в качестве примера):
import javax.inject.Inject;
public class Budget {
private final DB db;
@Inject
public Budget(DB data) {
this.db = data;
}
// same methods as above
}
Обратите внимание: конструктор аннотирован @Inject.
Затем мы должны настроить контейнер где-нибудь, когда приложение запускается:
Injector injector = Guice.createInjector(
new AbstractModule() {
@Override
public void configure() {
this.bind(DB.class).toInstance(
new Postgres("jdbc:postgresql:5740/main")
);
}
}
);
Некоторые платформы даже позволяют нам конфигурировать инжектор в XML-файле.
С этого момента нам не разрешается создавать Бюджет через оператор new, как мы это делали раньше. Вместо этого мы должны использовать инжектор, который мы только что создали:
public class App {
public static void main(String... args) {
Injection injector = // as we just did in the previous snippet
Budget budget = injector.getInstance(Budget.class);
System.out.println("Total is: " + budget.total());
}
}
Впрыск автоматически обнаруживает, что для создания экземпляра бюджета он должен предоставить аргумент для своего конструктора. Он будет использовать экземпляр класса Postgres, который мы инстанцировали в инжектор.
Это правильный и рекомендуемый способ использования Guice. Тем не менее есть несколько еще более темных рисунков, которые возможны, но не рекомендуются. Например, вы можете сделать свой инжектор одноточечным и использовать его прямо внутри класса Budget. Тем не менее, эти механизмы считаются неправильными даже разработчиками контейнеров DI, поэтому давайте их игнорировать и сосредоточимся на рекомендованном сценарии.
Для чего?
Позвольте мне повторить и суммировать сценарии неправильного использования контейнеров инъекции зависимостей:
- Инъекция полей
- Инъекциясеттера
- Передача инжектора в качестве зависимости
- Создание инжектором глобального синглтона
Если отложить все их в сторону, все, что у нас осталось, это инъекция конструктора, описанная выше. И как это нам помогает? Зачем нам это нужно? Почему мы не можем использовать простое старое new в основном классе приложения?
Созданный контейнер просто добавляет больше строк в базу кода или даже больше файлов, если мы используем XML. И он ничего не добавляет, кроме дополнительной сложности. Мы всегда должны помнить об этом, если у нас есть вопрос: «Какая база данных используется в качестве аргумента бюджета?»
Правильный путь
Теперь позвольте мне показать вам пример из реальной жизни использования new для построения приложения. Вот как мы создаем «мыслящий движок» в rultor.com (полный класс находится в Agents.java):
Созданный контейнер просто добавляет больше строк в базу кода или даже больше файлов, если мы используем XML. И он ничего не добавляет, кроме дополнительной сложности. Мы всегда должны помнить об этом, если у нас есть вопрос: «Какая база данных используется в качестве аргумента бюджета?»
Правильный путь
Теперь позвольте мне показать вам пример из реальной жизни использования new для построения приложения. Вот как мы создаем «мыслящий движок» в rultor.com (полный класс находится в Agents.java):
final Agent agent = new Agent.Iterative(
new Array<Agent>(
new Understands(
this.github,
new QnSince(
49092213,
new QnReferredTo(
this.github.users().self().login(),
new QnParametrized(
new Question.FirstOf(
new Array<Question>(
new QnIfContains("config", new QnConfig(profile)),
new QnIfContains("status", new QnStatus(talk)),
new QnIfContains("version", new QnVersion()),
new QnIfContains("hello", new QnHello()),
new QnIfCollaborator(
new QnAlone(
talk, locks,
new Question.FirstOf(
new Array<Question>(
new QnIfContains(
"merge",
new QnAskedBy(
profile,
Agents.commanders("merge"),
new QnMerge()
)
),
new QnIfContains(
"deploy",
new QnAskedBy(
profile,
Agents.commanders("deploy"),
new QnDeploy()
)
),
new QnIfContains(
"release",
new QnAskedBy(
profile,
Agents.commanders("release"),
new QnRelease()
)
)
)
)
)
)
)
)
)
)
)
),
new StartsRequest(profile),
new RegistersShell(
"b1.rultor.com", 22,
"rultor",
IOUtils.toString(
this.getClass().getResourceAsStream("rultor.key"),
CharEncoding.UTF_8
)
),
new StartsDaemon(profile),
new KillsDaemon(TimeUnit.HOURS.toMinutes(2L)),
new EndsDaemon(),
new EndsRequest(),
new Tweets(
this.github,
new OAuthTwitter(
Manifests.read("Rultor-TwitterKey"),
Manifests.read("Rultor-TwitterSecret"),
Manifests.read("Rultor-TwitterToken"),
Manifests.read("Rultor-TwitterTokenSecret")
)
),
new CommentsTag(this.github),
new Reports(this.github),
new RemovesShell(),
new ArchivesDaemon(
new ReRegion(
new Region.Simple(
Manifests.read("Rultor-S3Key"),
Manifests.read("Rultor-S3Secret")
)
).bucket(Manifests.read("Rultor-S3Bucket"))
),
new Publishes(profile)
)
);
Впечатляет? Это истинная композиция объекта. Я считаю, что именно так должно быть создано соответствующее объектно-ориентированное приложение.
И контейнеры DI? По-моему, они просто добавляют ненужный шум.
Комментариев нет:
Отправить комментарий