如何使用静态方法的依赖注入?

How to use Dependency Injection with Static Methods?

假设有一个带有实例Load()方法的Customer类。

当调用Load()方法时,它通过例如

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

GetAll()Order类的静态方法,输入参数是Customer类中定义的字段。

如您所见,OrderCustomer类的依赖项,但是,我不能只创建IOrder并将其注入,因为接口不能有静态方法。

因此,问题是我如何在这个例子中引入依赖注入?

我不想让GetAll()成为实例方法,因为它是一个静态方法,需要保持这种方式。

例如,我在设计中使用了实用程序类,其中大多数只包含静态方法。


如果必须保留静态方法,我将把静态调用包装在一个存储库对象中。

这样地:

1
2
3
4
5
6
7
8
9
10
interface IOrderRepository {
   IEnumerable<IOrder> GetAll(customerId, ..);
}

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

现在您将这个存储库注入到您的Customer类中。

(我假设您这样做是为了在运行时为测试目的注入假IOR。一般来说,静态方法是测试的一个严重障碍。)


将获取订单的聚合根视为您的客户模型,我强烈建议您创建一个客户存储库,并将其注入到任何服务需要它的地方。

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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();
    }
}


函数指针注入

TLDR:

Customer类中注入函数指针。此函数指针的值在生产中可以是Order.GetAll,在测试中可以是MockOrder.GetAll

例子:

依赖项(我们依赖的有问题的静态函数):

1
2
3
4
5
6
class Order {
    static func GetAll() -> [Order] {
        var orders = ... // Load from production source
        return orders
    }
}

我们的从属类(取决于静态函数):

1
2
3
4
5
6
7
8
9
10
class Customer {
    func Init(getAllOrdersFunction) { // Arg is a func pointer
        self.getAllOrdersFunction = getAllOrdersFunction
    }

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

生产客户端类(执行依赖项注入):

1
2
3
4
5
6
7
class BusinessLogicManager {
    func DoBusinessLogic() {
        var customer = Customer(Order.GetAll) // Prod func injected here
        customer.Load()
        // Do stuff...
    }
}

测试客户端类(单元测试如何插入假依赖项):

1
2
3
4
5
6
7
8
9
10
11
12
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对象,或使用Paul Phillips的答案),可能会更容易。但是如果你真的想把它保持为一个静态函数,那么这个解决方案就可以工作了。