Inversion Of Control является одним из наиболее распространенных и широко используемых методов обработки зависимостей классов при разработке программного обеспечения и может легко стать наиболее важной практикой в модульном тестировании. По сути, он определяет, является ли ваш код модульно-тестируемым или нет. Не только это, но также может помочь значительно улучшить общую структуру и дизайн программного обеспечения. Но о чем это все? Это действительно так важно? Надеюсь, мы очистим их в следующих строках
Выявление Зависимостей Класса
Как мы упоминали ранее, Inversion Of Control — это метод, используемый для эффективной обработки зависимостей классов; но что именно является зависимостью ? В реальной жизни, например, автомобилю нужен двигатель для работы; без этого это, вероятно, не будет работать вообще. В программировании это одно и то же; когда классу нужен другой класс для правильного функционирования, он зависит от него. Это называется зависимостью класса или связью .
Давайте посмотрим на следующий пример кода:
public class UserManager { private Md5PasswordHasher passwordHasher; public UserManager() { this.passwordHasher = new Md5PasswordHasher(); } public void ResetPassword(string userName, string password) { // Get the user from the database User user = DataContext.Users.GetByName(userName); string hashedPassword = this.passwordHasher.Hash(password); // Set the user new password user.Password = hashedPassword; // Save the user back to the database. DataContext.Users.Update(user); DataContext.Commit(); } // More methods... } public class Md5PasswordHasher { public string Hash(string plainTextPassword) { // Hash password using an encryption algorithm... } }
Предыдущий код описывает два класса, UserManager
и PasswordHasher
. Мы можем видеть, как UserManager
класс инициализирует новый экземпляр PasswordHasher
класса в своем конструкторе и сохраняет его как переменную уровня класса, чтобы все методы в классе могли использовать его (строка 3). Метод, на котором мы собираемся сосредоточиться, — это ResetPassword
метод. Как вы уже могли заметить, строка 15 подсвечивается. Эта строка использует PasswordHasher
экземпляр, следовательно, отмечая сильную зависимость класса между UserManager
и PasswordHasher
.
Не звоните нам, мы вам позвоним
Когда класс создает экземпляры своих зависимостей, он знает, какую реализацию этой зависимости использует и, вероятно, как она работает. Класс управляет своим поведением. Используя инверсию управления, любой, кто использует этот класс, может указать конкретную реализацию каждой из используемых им зависимостей; на этот раз пользователь класса — тот, кто частично контролирует поведение класса (или то, как он ведет себя в тех частях, где он использует предоставленные зависимости).
В любом случае, все это довольно запутанно. Давайте посмотрим на пример:
public class UserManager { private IPasswordHasher passwordHasher; public UserManager(IPasswordHasher passwordHasher) { this.passwordHasher = passwordHasher; } public void ResetPassword(string userName, string password) { // Get the user from the database User user = DataContext.Users.GetByName(userName); string hashedPassword = this.passwordHasher.Hash(password); // Set the user new password user.Password = hashedPassword; // Save the user back to the database. DataContext.Users.Update(user); DataContext.Commit(); } // More methods... } public interface IPasswordHasher { string Hash(string plainTextPassword); } public class Md5PasswordHasher : IPasswordHasher { public string Hash(string plainTextPassword) { // Hash password using an encryption algorithm... } }
Инверсия управления обычно реализуется путем применения шаблона проектирования, называемого шаблоном стратегии (как определено в книге «Банда четырех» ). Этот шаблон состоит в абстрагировании конкретных реализаций компонентов и алгоритмов от остальных классов путем предоставления только интерфейса, который они могут использовать; таким образом, делая реализации взаимозаменяемыми во время выполнения и инкапсулируя, как эти реализации работают, так как любой класс, использующий их, не должен заботиться о том, как они работают.
Итак, чтобы достичь этого, нам нужно разобраться в некоторых вещах:
- Абстрагировать интерфейс от
Md5PasswordHasher
классаIPasswordHasher
; так что любой может написать пользовательские реализации хэшей паролей (строки 28-31). - Отметьте
Md5PasswordHasher
класс как реализациюIPasswordHasher
интерфейса (строка 33). - Измените тип используемого хэша пароля
UserManager
наIPasswordHasher
(строка 3). - Добавьте новый параметр конструктора типа
IPasswordHasher
interface (строка 5), который является экземпляром, которыйUserManager
класс будет использовать для хеширования своих паролей. Таким образом, мы делегируем создание зависимостей пользователю класса и позволяем пользователю предоставлять любую реализацию, которую он хочет, позволяя ему контролировать способ хэширования пароля.
В этом и заключается сама суть инверсии управления: минимизировать сцепление классов. Пользователь UserManager
класса теперь может контролировать, как хэшируются пароли. Управление хэшированием пароля было инвертировано из класса в пользователя. Вот пример того, как мы можем указать единственную зависимость UserManager
класса:
IPasswordHasher md5PasswordHasher = new Md5PasswordHasher(); UserManager userManager = new UserManager(md5PasswordHasher); userManager.ResetPassword("luis.aguilar", "12345");
Итак, почему это полезно? Что ж, мы можем сойти с ума и создать собственную реализацию хеширования, которая будет использоваться классом UserManager:
// Plain text password hasher: public class PlainTextPasswordHasher : IPasswordHasher { public string Hash(string plainTextPassword) { // Let's disable password hashing by returning // the plain text password. return plainTextPassword; } } // Usage: IPasswordHasher plainTextPasswordHasher = new PlainTextPasswordHasher(); UserManager userManager = new UserManager(plainTextPasswordHasher); // Resulting password will be: 12345. userManager.ResetPassword("luis.aguilar", "12345");
Вывод
На этом мы завершаем нашу статью об инверсии контроля. Надеюсь, немного потренировавшись, вы сможете начать применять это к своему коду. Конечно, самое большое преимущество этого метода связано с модульным тестированием. Итак, какое отношение это имеет к юнит-тестированию? Ну, мы увидим это, когда начнем насмехаться над типами . Так что следите за обновлениями!