运行时已知参数的依赖注入?

发布于 2024-10-17 09:34:32 字数 2920 浏览 2 评论 0原文

我使用 IoC 容器已经有一段时间了,但今天我发现代码中一遍又一遍地出现一些“模式”。为了给你一些背景知识,我现在正在开发基本上用于数据分析的网络应用程序。那里有一组功能,要求用户从一开始就选择我们所说的QueryTypeContex。一旦选择了此查询类型,就可以采取其他步骤,但所有步骤都在此特定的QueryTypeContex中执行。在 GUI 中,QueryTypeContex 拾取表示为使用其他控件打开新选项卡。

当用户使用给定的 QueryTypeContex 时,对服务器的所有 ajax 调用都包含标识用户选择的 QueryTypeId 并用于在服务器上构建 QueryTypeContex then 用于各种数据检索和操作。

我发现我们的许多使用 Ioc 容器构建的控制器(我们使用 asp.net mvc)都有一个共同点。有一个看起来有点像这样的操作方法:

public class AttributeController : Controller 
{
  public AttributeController(IUsefulService usefulService)
  {
     _usefulservice = usefulService;
  }
  ActionResult GetAttributes(QueryTypeContex context)
  {
    var dataDto = _usefulService.Manipulate(context, currentUser);
    return JSon(dataDto);
  }
  ...
}

为了将 QueryTypeContex 绑定到操作参数,我们使用自定义模型绑定器从数据库中提取一些信息。一旦服务获取 QueryTypeContex 作为参数,它就会将其或其属性传递给实例数据访问层的方法参数中的协作者。因此,有一个看起来像这样的工厂类

public interface IDateValueFactory 
{
   DateValue CurrentYear(QueryTypeContex context);
   DateValue RollingMonth(int numberOfMonths, QueryTypeContex context);
   DateValue RollingQuareter(int numberOfQuarters, QueryTypeContex context);
}

public class DateValueFactory : IDateValueFactory 
{
  public DateValueFactory(IDateValueDb dateValueDb)
  {
    _dateValueDb = dateValueDb;
  }

  public DateValue CurrentYear(QueryTypeContext context)
  {
    var currentYear = _dateValueDb.GetCurrentYear(context.Id);
    return new DateValue(DateValueType.CurrentYear, currentYear, context);
  }

  public DateValue RollingMonth(int numberOfMonths, QueryTypeContex context)
  {
    return new DateValue(DateValueType.RollingMonth, numberOfMonths, context);
  }
  ...

}

正如您所看到的,所有这些方法都获取 QueryTypeContex 作为参数,更重要的是,它们都获取相同的 QueryTypeContex 实例> 在其短暂的生命中(一个网络请求)。所以我开始想知道是否可以重构它,以便每当许多服务类方法需要 QueryTypeContex 作为参数时,它都会通过构造函数注入,而不是再次传递相同的值。例如:

public interface IDateValueFactory 
{
   DateValue CurrentYear();
   DateValue RollingMonth(int numberOfMonths);
   DateValue RollingQuareter(int numberOfQuarters);
}

public class DateValueFactory : IDateValueFactory 
{
  public DateValueFactory(IDateValueDb dateValueDb, QueryTypeContext context)
  {
    _dateValueDb = dateValueDb;
    _context = context;
  }

  public DateValue CurrentYear()
  {
    var currentYear = _dateValueDb.GetCurrentYear(_context.Id);
    return new DateValue(DateValueType.CurrentYear, currentYear, _context);
  }

  public DateValue RollingMonth(int numberOfMonths)
  {
    return new DateValue(DateValueType.RollingMonth, numberOfMonths, _context);
  }
  ...

}

现在真正的问题是: 对于这类事情来说这是一个好主意还是违反了我应该遵守的一些设计原则?

为了注入使用来自http请求的信息构建的QueryTypeContex实例,我考虑将 QueryTypeId 嵌入 uri 中,以便它可以在服务器上的 RouteData 中使用。然后,在构建控制器之前,我可以将其拉出,构建 QueryTypeContex ,为该请求创建嵌套的 IoC 容器并将其注入到容器中。然后,每当某个类需要 QueryTypeContex 来执行其工作时,它就会简单地将其声明为构造函数参数。

I've been using IoC container's for quite some time but today I've found some "pattern" appearing in code over and over again. To give you some background I am now working on web application basically used for data analysis. There is a set of features there, that requires user to pick up what we call QueryTypeContex at the very beginning. Once this query type is chosen other steps may be taken but that all are performed in this specific QueryTypeContex. In the gui the QueryTypeContex pick up is represented as opening new tab with other controls.

When user is working with given QueryTypeContex all ajax calls to the server include QueryTypeId that identifies users choice and is used to build QueryTypeContex on the server which then is used for various data retrieval and manipulation.

What I've found is that many of our controllers (we use asp.net mvc) that are constructed with Ioc container have one thing in common. There is an action method that looks somewhat like this:

public class AttributeController : Controller 
{
  public AttributeController(IUsefulService usefulService)
  {
     _usefulservice = usefulService;
  }
  ActionResult GetAttributes(QueryTypeContex context)
  {
    var dataDto = _usefulService.Manipulate(context, currentUser);
    return JSon(dataDto);
  }
  ...
}

In order to bind QueryTypeContex to action argument we use custom model binder that pulls some information from database. Once the service gets QueryTypeContex as argument it passes it or its properties down to its collaborators in method arguments for instance data access layer. And so there is a factory class that looks like this

public interface IDateValueFactory 
{
   DateValue CurrentYear(QueryTypeContex context);
   DateValue RollingMonth(int numberOfMonths, QueryTypeContex context);
   DateValue RollingQuareter(int numberOfQuarters, QueryTypeContex context);
}

public class DateValueFactory : IDateValueFactory 
{
  public DateValueFactory(IDateValueDb dateValueDb)
  {
    _dateValueDb = dateValueDb;
  }

  public DateValue CurrentYear(QueryTypeContext context)
  {
    var currentYear = _dateValueDb.GetCurrentYear(context.Id);
    return new DateValue(DateValueType.CurrentYear, currentYear, context);
  }

  public DateValue RollingMonth(int numberOfMonths, QueryTypeContex context)
  {
    return new DateValue(DateValueType.RollingMonth, numberOfMonths, context);
  }
  ...

}

As you see all of these methods get QueryTypeContex as a parameter more importantly they all get the very same instance of QueryTypeContex during their short life (one web request). So I started to wonder if I could refactor this so that whenever many service class methods require QueryTypeContex as arguments it would be injected via constructor instead of passing the same value over an over again. For example:

public interface IDateValueFactory 
{
   DateValue CurrentYear();
   DateValue RollingMonth(int numberOfMonths);
   DateValue RollingQuareter(int numberOfQuarters);
}

public class DateValueFactory : IDateValueFactory 
{
  public DateValueFactory(IDateValueDb dateValueDb, QueryTypeContext context)
  {
    _dateValueDb = dateValueDb;
    _context = context;
  }

  public DateValue CurrentYear()
  {
    var currentYear = _dateValueDb.GetCurrentYear(_context.Id);
    return new DateValue(DateValueType.CurrentYear, currentYear, _context);
  }

  public DateValue RollingMonth(int numberOfMonths)
  {
    return new DateValue(DateValueType.RollingMonth, numberOfMonths, _context);
  }
  ...

}

And now the real question:
Is this a good idea to to this sort of thing or it violates some design principles i should adhere to ?

In order to inject QueryTypeContex instance, builded using information from http request I thought about embedding the QueryTypeId in the uris so it would be available in the RouteData on the server. Then before the controller is constructed I could pull it out, build the QueryTypeContex, create nested IoC container for that request and inject it into the container. Then whenever some class would need QueryTypeContex to perform its job it would simply declare it as constructor argument.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

草莓酥 2024-10-24 09:34:32

任何可以有意义地作为依赖项推送到构造函数的东西,都应该这样做。通过构造函数注入连接的依赖项是实现细节,而方法参数是模型 API 的一部分。

重构通过构造函数连接的依赖项比更改 API 容易得多,因此出于可维护性的原因,您应该选择尽可能少的方法参数。

Anything you can meaningfully push to the constructor as dependencies, you should. Dependencies wired up with constructor injection are implementation details, whereas method parameters are part of your model's API.

It's much easier to refactor dependencies wired through constructors than to change an API, so for maintainability reasons you should prefer as few method parameters as possible.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文