Статьи

Внедрение зависимостей Миф: прохождение ссылок

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

Допустим, у вас есть 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)
  • Если вы просите что-то, что вы не используете напрямую, то вы нарушаете Закон Деметры . Что на самом деле означает, что вы либо пытаетесь создать объект самостоятельно, либо параметр должен быть передан через конструктор, а не через вызов метода. (Мы можем подробнее об этом поговорить в другом сообщении в блоге)

Так куда же ушли все новые операторы , спросите вы? Ну, мы уже ответили на этот вопрос здесь . И с этим я надеюсь, что мы положили конец мифу!

Кстати, для тех из вас, кто задается вопросом, почему это распространенное заблуждение, причина в том, что люди ошибочно полагают, что граф зависимостей конструктора и граф вызовов по своей сути идентичны (см. Этот пост ). Если вы строите свои объекты в строке (как это делают большинство разработчиков, подумайте о новом операторе ), тогда да, эти два графика очень похожи. Однако, если вы отделяете экземпляр объекта графа от его выполнения, то эти два графика независимы. Эта независимость — это то, что позволяет нам вводить зависимости непосредственно там, где они необходимы, без передачи ссылки через посредническое сотрудничество.

С http://misko.hevery.com/