После прочтения статьи о синглетонах (анти-паттерне разработки) и о том, как они действительно являются глобальными переменными, и предложении внедрения зависимостей просто передать ссылку на синглтон в конструкторе (вместо того, чтобы искать их в глобальном состоянии ), многие люди неправильно пришел к выводу, что теперь им нужно проехать синглтон повсюду. Позвольте мне продемонстрировать миф на следующем примере.
Допустим, у вас есть LoginPage, который использует UserRepository для аутентификации. UserRepository, в свою очередь, использует Database Singleton для получения глобальной ссылки на соединение с базой данных, например:
class UserRepository { private static final BY_USERNAME_SQL = "Select ..."; User loadUser(String user) { Database db = Database.getInstance(); return db.query(BY_USERNAME_SQL, user); } } class LoginPage { UserRepository repo = new UserRepository(); login(String user, String pwd) { User user = repo.loadUser(user); if (user == null || user.checkPassword(pwd)) { throw new IllegalLoginException(); } } }
Первая мысль заключается в том, что если вы последуете совету внедрения зависимостей, вам нужно будет передать базу данных в LoginPage, чтобы вы могли передать ее в UserRepository. Аргумент гласит, что этот вид кодирования сделает код сложным для сопровождения и понимания. Давайте посмотрим, как это будет выглядеть после того, как мы избавимся от глобальной переменной.
Во-первых, давайте посмотрим на UserRepository.
class UserRepository { private static final BY_USERNAME_SQL = "Select ..."; private final Database db; UserRepository(Database db) { this.db = db; } User loadUser(String user) { return db.query(BY_USERNAME_SQL, user); } }
Обратите внимание, как удаление глобального поиска Singleton очистило код. Этот код теперь легко тестировать, поскольку в тесте мы можем создать новый UserRepository и передать поддельное соединение с базой данных в конструктор. Это улучшает тестируемость. Раньше у нас не было возможности перехватывать вызовы к базе данных, и, следовательно, мы никогда не могли проверять фальшивку базы данных. Мы не только не имели возможности перехватывать вызовы к базе данных, мы даже не знали, глядя на API, что база данных задействована. (см. « Синглтоны — патологические логова» ). Я надеюсь, что все согласятся с тем, что это изменение явной передачи ссылки на базу данных значительно улучшает код.
Теперь давайте посмотрим, что происходит с LoginPage …
class LoginPage { UserRepository repo; LoginPage(Database db) { repo = new UserRepository(db); } login(String user, String pwd) { User user = repo.loadUser(user); if (user == null || user.checkPassword(pwd)) { throw new IllegalLoginException(); } } }
Поскольку UserRepository больше не может выполнять глобальный поиск, чтобы захватить базу данных, он должен запросить ее в конструкторе. Поскольку LoginPage выполняет конструкцию, теперь ему нужно запросить Databse, чтобы он мог передать его конструктору UserRepository. Миф, который мы здесь описываем, говорит о том, что это затрудняет понимание и поддержку кода. Угадай, что?! Миф верен! Код в его нынешнем виде трудно поддерживать и понимать. Означает ли это, что внедрение зависимости неправильно? НЕТ! это означает, что вы сделали только половину работы! В том , как думать о новом операторамы углубимся в детали, почему важно отделить вашу бизнес-логику от новых операторов. Обратите внимание, как LoginPage нарушает это. Он вызывает новый пользовательский репозиторий. Проблема в том, что LoginPage нарушает закон Деметры . LoginPage запрашивает базу данных, даже если сама база данных ей не нужна (это сильно затрудняет тестируемость, как описано здесь ). Вы можете сказать, так как LoginPage не вызывает какой-либо метод в базе данных. Этот код, как и предполагает миф, плох! Так как же это исправить?
Мы исправляем это, делая больше инъекций зависимости.
class LoginPage { UserRepository repo; LoginPage(UserRepository repo) { this.repo = repo; } login(String user, String pwd) { User user = repo.loadUser(user); if (user == null || user.checkPassword(pwd)) { throw new IllegalLoginException(); } } }
LoginPage нужен UserRepository. Таким образом, вместо того, чтобы пытаться создать сам UserRepository, он должен просто запросить UserRepository в конструкторе. Тот факт, что UserRepository нужна ссылка на базу данных, не является проблемой LoginPage. Кроме того, LoginPage не заботится о том, как создать UserRepository. Обратите внимание, что этот LoginPage теперь чище и проще для тестирования. Для тестирования мы можем просто создать экземпляр LoginPage и передать поддельный UserRepository, с помощью которого мы можем эмулировать то, что происходит при успешном входе в систему, а также при неудачном входе в систему и / или исключениях. Это также хорошо заботится о беспокойстве этого мифа. Обратите внимание, что каждый объект просто знает об объектах, с которыми он напрямую взаимодействует. Там нет передачи объектов ссылки только для того, чтобы привести их в нужное место, где они необходимы. Если вы попадаете в этот миф, то все это означает, что вы не полностью применили внедрение зависимостей.
Итак, вот два правила внедрения зависимостей:
- Всегда просите ссылку! (не создавайте и не ищите ссылку в глобальном пространстве, известную как анти-шаблон Singleton)
- Если вы просите что-то, что вы не используете напрямую, то вы нарушаете Закон Деметры . Что на самом деле означает, что вы либо пытаетесь создать объект самостоятельно, либо параметр должен быть передан через конструктор, а не через вызов метода. (Мы можем подробнее об этом поговорить в другом сообщении в блоге)
Так куда же ушли все новые операторы , спросите вы? Ну, мы уже ответили на этот вопрос здесь . И с этим я надеюсь, что мы положили конец мифу!
Кстати, для тех из вас, кто задается вопросом, почему это распространенное заблуждение, причина в том, что люди ошибочно полагают, что граф зависимостей конструктора и граф вызовов по своей сути идентичны (см. Этот пост ). Если вы строите свои объекты в строке (как это делают большинство разработчиков, подумайте о новом операторе ), тогда да, эти два графика очень похожи. Однако, если вы отделяете экземпляр объекта графа от его выполнения, то эти два графика независимы. Эта независимость — это то, что позволяет нам вводить зависимости непосредственно там, где они необходимы, без передачи ссылки через посредническое сотрудничество.