The Light
The Light

Reputation: 27011

How to use Dependency Injection with Static Methods?

Imagine there is a Customer class with an instance Load() method.

When the Load() method is called, it retrieves order details by e.g.

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

GetAll() is a static method of the Order class and the input parameters are fields defined in the Customer class.

As you can see, Order is a dependency of the Customer class, however, I can't just create an IOrder and inject it there as interfaces can't have static methods.

Therefore, the question is how could I introduce dependency injection in this example?

I don't want to make GetAll() an instance method since it's a static method and need to keep it that way.

For example, I have used utility classes in my design, most of which just contain static methods.

Upvotes: 24

Views: 32287

Answers (3)

tboyce12
tboyce12

Reputation: 1481

Function Pointer Injection

TLDR:

Inject a function pointer into the Customer class. The value of this function pointer can be Order.GetAll in production, and MockOrder.GetAll in tests.

EXAMPLE:

The dependency (problematic static function we depend on):

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

Our dependent class (depends on static function):

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

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

Production client class (performs the dependency injection):

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

Testing client class (how unit test can inject a fake dependency):

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
    }
}

DISCUSSION:

How you actually inject the "function pointer" will depend on the syntax and features available in your language. Here I'm just talking about the general concept.

This isn't exactly a pretty solution. It would probably be easier if you can change GetAll to be an instance method (perhaps by introducing an OrdersLoader object, or by using Paul Phillips' answer). But if you really want to keep it as a static function, then this solution will work.

Upvotes: 3

Mike
Mike

Reputation: 4287

Seeing as your aggregate root for fetching orders is your customer model I would strongly advise you create a customer repository and inject that to whatever service requires it.

Here is an example:

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();
    }
}

Upvotes: 4

Paul Phillips
Paul Phillips

Reputation: 6259

If you must keep the static method, I would wrap the static calls in a Repository object.

Like this:

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

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

Now you inject this repository into your Customer class.

(I'm assuming you're doing this so you can inject fake IOrders at runtime for testing purposes. I should say that in general, static methods are a serious obstacle to testing.)

Upvotes: 14

Related Questions