0

Я читал о том, как написать тестируемый код и наткнулся на шаблон проектирования зависимостей.Включение зависимостей дальше по «цепочке»

Этот шаблон дизайна очень прост для понимания, и для него нет ничего, объект запрашивает значения, а не создает их сам.

Однако теперь, когда я думаю о том, как это можно использовать, приложение im currenty working on я понимаю, что есть некоторые сложности для него. Представьте себе следующий пример:

public class A{ 

    public string getValue(){ 
     return "abc"; 
    } 
} 


public class B{ 
    private A a; 

    public B(A a){ 
     this.a=a; 
    } 
    public void someMethod(){ 
     String str = a.getValue(); 
    } 
} 

Модульное тестирование someMethod() теперь будет легко, так как я могу создать макет из A и имеют getValue() возвращение все, что я хочу.

Зависимость класса B от A вводится через конструктор, но это означает, что A должен быть создан вне класса B, поэтому эта зависимость переместилась в другой класс. Это будет повторяться много слоев вниз, и в какой-то момент нужно создать экземпляр.

Теперь на вопрос, верно ли, что при использовании Injection Dependency вы продолжаете передавать зависимости через все эти слои? Разве это не сделает код менее читаемым и потребует много времени для отладки? И когда вы достигнете «верхнего» слоя, как вы можете тестировать этот класс?

+0

Это не инъекция зависимости. Вы должны создать интерфейс, после выполнения его реализации и вместо частного A a в своем классе b использовать private IA ia, затем передать объект-конструктор A и присвоить его переменной ia –

ответ

3

Ну да, это означало бы, что вы должны передавать зависимости по всем слоям. Тем не менее, именно здесь нужны контейнеры Inversion of Control. Они позволяют регистрировать все компоненты (классы) в системе. Затем вы можете запросить контейнер IoC для экземпляра class B (в вашем примере), который автоматически вызовет правильный конструктор для автоматического создания любых объектов, от конструктора зависит (в вашем случае class A).

Приятная обсуждение можно найти здесь: Why do I need an IoC container as opposed to straightforward DI code?

4

Я надеюсь, что я правильно понимаю ваш вопрос.

инъекционные Зависимости

Нет, мы не передаем зависимости через все слои. Мы передаем их только слоям, которые напрямую общаются с ними. Например:

public class PaymentHandler { 

    private customerRepository; 

    public PaymentHandler(CustomerRepository customerRepository) { 
     this.customerRepository = customerRepository; 
    } 

    public void handlePayment(CustomerId customerId, Money amount) { 
     Customer customer = customerRepository.findById(customerId); 
     customer.charge(amount); 
    } 
} 

public interface CustomerRepository { 
    public Customer findById(CustomerId customerId); 
} 

public class DefaultCustomerRepository implements CustomerRepository { 

    private Database database;  

    public CustomerRepository(Database database) { 
     this.database = database; 
    } 

    public Customer findById(CustomerId customerId) { 
     Result result = database.executeQuery(...); 
     // do some logic here 
     return customer; 
    } 
} 

public interface Database { 
    public Result executeQuery(Query query); 
} 

PaymentHandler не знает о Database, он говорит только на CustomerRepository. Инъекция Database останавливается на уровне хранилища.

читаемость кода

При выполнении ручной инъекции без рамок или библиотек, чтобы помочь, мы могли бы в конечном итоге с классами фабрики, которые содержат много шаблонного кода, как return new D(new C(new B(), new A());, которые в какой-то момент может быть менее читаемым. Для решения этой проблемы мы, как правило, используем рамки DI, такие как Guice, чтобы избежать написания многих заводов.

Однако для классов, на самом деле выполняющих бизнес/бизнес-логику, они должны быть более читабельными и понятными, поскольку они говорят только с их непосредственными сотрудниками и выполняют работу, которую они должны выполнять.

Модульное тестирование

Я полагаю, что слой "Top" вы имеете в виду PaymentHandler класс. В этом примере мы можем создать класс stub CustomerRepository и вернуть ему объект Customer, с которым мы можем проверить, а затем передать заглушку на PaymentHandler, чтобы проверить, заряжена ли правильная сумма.

Общая идея состоит в том, чтобы передать поддельные коллабораторы для контроля их вывода, чтобы мы могли безопасно утверждать поведение тестируемого класса (в этом примере класс PaymentHandler).

Почему интерфейсы

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

Надеюсь, это поможет.

1

ИМО, ваш вопрос демонстрирует, что вы понимаете образец.

Используется правильно, у вас будет Composition Root, где все зависимости разрешаются и вводятся. Использование контейнера IoC здесь разрешает зависимости и передает их через слои для вас.

Это прямо противоположно шаблону обслуживания, которое многие считают анти-шаблоном.

Использование композиции Корень не должен делать ваш код менее читабельным/понятным, поскольку хорошо продуманные классы с четкими и релевантными зависимостями должны быть достаточно самодокументируемыми. Я не уверен в модульном тестировании корня композиции. Он имеет сдержанную роль, поэтому его следует проверить.