Как использовать инъекцию зависимостей со статическими методами? - PullRequest
12 голосов
/ 29 июня 2011

Представьте, что существует класс Customer с экземпляром Load() метода.

Когда вызывается метод Load(), он получает информацию о заказе, например,

var orders = Order.GetAll(customerId, ...);

GetAll() - это статический метод класса Order, а входные параметры - это поля, определенные в классе Customer.

Как видите, Order является зависимостью класса Customer, однако я не могу просто создать IOrder и вставить его туда, поскольку интерфейсы не могут иметь статические методы.

Следовательно, вопрос в том, как я могу ввести внедрение зависимостей в этом примере?

Я не хочу делать GetAll() методом экземпляра, так как это статический метод, и его нужно сохранять таким образом.

Например, я использовал в своем дизайне служебные классы, большинство из которых просто содержат статические методы.

Ответы [ 3 ]

12 голосов
/ 29 июня 2011

Если вы должны сохранить статический метод, я бы обернул статические вызовы в объект Repository.

Примерно так:

interface IOrderRepository {
   IEnumerable<IOrder> GetAll(customerId, ..);
}

class OrderRepository : IOrderRepository {
   IEnumerable<IOrder> GetAll(customerId, ...)
   {
     Order.GetAll(customerId,...); // The original static call.
   }
}

Теперь вы добавляете этохранилище в ваш Customer класс.

(я предполагаю, что вы делаете это, чтобы вы могли вводить поддельные IOrders во время выполнения для целей тестирования. Я должен сказать, что в целом статические методы являются серьезным препятствием длятестирование.)

3 голосов
/ 01 июля 2011

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

Вот пример:

public class CustomerService
{
    private readonly ICustomerRepository _customerRepository;

    public CustomerService(ICustomerRepository customerRepository)
    {
        if (customerRepository == null)
        {
            throw new ArgumentNullException("customerRepository");
        }

        _customerRepository = customerRepository;
    }

    public IEnumerable<IOrder> GetOrdersForCustomerId(int customerId)
    {
        return _customerRepository.GetOrdersForCustomerId(customerId);
    }
}

public interface ICustomerRepository
{
    IEnumerable<IOrder> GetOrdersForCustomerId(int customerId);
}

class CustomerRepository : ICustomerRepository
{
    public IEnumerable<IOrder> GetOrdersForCustomerId(int customerId)
    {
        throw new NotImplementedException();
    }
}
1 голос
/ 03 августа 2017

Ввод указателя функции

TLDR:

Ввод указателя функции в класс Customer.Значение этого указателя функции может составлять Order.GetAll в производстве и MockOrder.GetAll в тестах.

ПРИМЕР:

Зависимость ( проблемная статическая функция, от которой мы зависим ):

class Order {
    static func GetAll() -> [Order] {
        var orders = ... // Load from production source
        return orders
    }
}

Наш зависимый класс ( зависит от статической функции ):

class Customer {
    func Init(getAllOrdersFunction) { // Arg is a func pointer
        self.getAllOrdersFunction = getAllOrdersFunction
    }

    func Load() {
        var orders = self.getAllOrdersFunction()
        // Do stuff...
    }
}

Рабочий класс клиента ( выполняет внедрение зависимостей ):

class BusinessLogicManager {
    func DoBusinessLogic() {
        var customer = Customer(Order.GetAll) // Prod func injected here
        customer.Load()
        // Do stuff...
    }
}

Проверка класса клиента ( как модульный тест может внедрить ложную зависимость ):

class CustomerUnitTests {
    static func GetFakeOrders() {
        var orders = ... // Hardcoded test data
        return orders
    }

    func TestLoad() {
        var customer = Customer(CustomerUnitTests.GetFakeOrders) // Fake func injected here
        customer.Load()
        // Verify results given known behavior of GetFakeOrders
    }
}

ОБСУЖДЕНИЕ:

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

Это не совсем симпатичное решение.Вероятно, было бы проще, если бы вы могли изменить GetAll на метод экземпляра (возможно, введя объект OrdersLoader или воспользовавшись ответом Пола Филлипса).Но если вы действительно хотите сохранить его как статическую функцию, то это решение будет работать.

...