【问题标题】:How to use Dependency Injection with Static Methods?如何将依赖注入与静态方法一起使用?
【发布时间】:2011-09-25 07:01:09
【问题描述】:

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

Load()方法被调用时,它通过e.g.检索订单详情

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

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

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

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

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

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

【问题讨论】:

    标签: dependency-injection static-methods


    【解决方案1】:

    如果您必须保留静态方法,我会将静态调用包装在 Repository 对象中。

    像这样:

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

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

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

    【讨论】:

    • 谢谢,这些存储库类和接口会成为数据访问层的一部分吗?我的 Order 类是从 Order 表映射的业务实体(使用 LINQ to SQL)。
    • @user133212 它只是您现有功能的包装。因此,无论该功能在逻辑上属于哪一层,我也会放置包装器。
    • 这似乎毫无意义。整个想法是不必制作一个对象
    【解决方案2】:

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

    这是一个例子:

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

    【讨论】:

      【解决方案3】:

      函数指针注入

      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 对象或使用Paul Phillips 的答案),可能会更容易。但是如果你真的想把它作为一个静态函数,那么这个解决方案就可以了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-06-06
        • 1970-01-01
        • 2018-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-09
        相关资源
        最近更新 更多