在c#中创建注入构造函数的接口的最好方法是什么?

本文关键字:方法 是什么 接口 创建 注入 构造函数 | 更新日期: 2023-09-27 17:50:15

我在一个项目中,外部数据是从不同的来源,如数据库,3外部web api, web配置获取。为了避免紧耦合,在类构造函数中使用并传递了一些接口,例如:

public Dog(IDataAccess dataAccess, IConverter converter, IConfigAccess configAccess,
    ITimezoneAccess timezoneAccess)
public Cat(IDataAccess dataAccess, IConverter converter, IConfigAccess configAccess, 
    ITimezoneAccess timezoneAccess)
public Duck(IDataAccess dataAccess, IConverter converter, IConfigAccess configAccess, 
    ITimezoneAccess timezoneAccess)

它帮助我们进行单元测试,因为我们创建了这些接口的模拟实现。

在开发代码时,所有类之间存在一些公共函数,例如Datetime操作,Fixed values方法等。我决定创建一些静态类,将此功能划分为特定的类,如DatetimeHelper、FixedCalculationsHelper、StringHandlingHelper等。

我得到了避免使用这些静态类的建议,并将它们转换为带有接口的策略,并将它们作为其他外部数据访问接口传递给构造函数。

  1. 当我应用这个时,我的类的构造函数将有很多接口参数,例如:

    public Dog(IDataAccess dataAccess, IConverter converter, IConfigAccess configAccess)ITimezoneAccess;

处理这种情况的最优雅/最好的方法是什么?(不确定这里是否使用了一些技术,如容器或类似的东西)

  • 为什么最好将这个静态类转换为接口/实现策略(即使这个方法是静态的,如CalculateArea(int value1, int value2))?
  • 欢迎任何评论或解释。提前感谢。

    在c#中创建注入构造函数的接口的最好方法是什么?

    使用接口的目的是要对抽象进行编码,而不是对消除依赖关系的具体进行编码。

    1. 向构造函数传递多个接口是可以的,但是你不希望传入具体类。如果你不想让构造函数有参数,可以使用setter注入而不是构造函数注入。

      public class Duck
      {
          IDataAccess DataAccess { get; set; }
          IConverter Converter { get; set; }
          IConfigAccess ConfigAccess { get; set; }
          ITimezoneAccess TimezoneAccess { get; set; }
          public Duck()
          {
               // parameterless contructor 
          }
      }
      
    2. 通过使用接口更改实现将容易得多。它使您能够更好地控制程序的结构。您希望您的类对扩展开放,但对修改关闭,这就是开闭原则。在我看来,我会让helper扩展方法,而放弃为它们创建接口。

    我们使用依赖注入来允许代码松散耦合。松散耦合的代码使我们的代码非常灵活。它允许我们的代码被隔离地测试,允许代码被独立地部署,允许我们拦截或修饰类,而不必在整个应用程序中进行彻底的更改。

    但是你不需要对类的每个依赖项都使用这些特征。对于简单的助手方法,它们本身没有任何依赖关系,永远不需要替换、修饰或拦截,并且不会使测试复杂化,因此几乎没有必要将它们提升为完整的组件并将它们隐藏在抽象之后。您很快就会发现,您想要单独测试消费类,但要使用真正的助手逻辑。现在,在单元测试中,您将很难将所有这些连接起来。

    我的建议是不要过度。

    话虽如此,即使你不注入那些简单的帮助方法,你的类仍然可能有很大的构造函数。有很多依赖的构造函数是一种代码气味。这表明这样的类违反了单一职责原则(SRP),这意味着一个类有太多的职责。违反SRP会导致代码难以维护和测试。

    修复SRP违规并不总是容易的,但是有几个模式和实践可以帮助您改进设计。

    重构聚合服务就是这些实践之一。如果一个类有许多依赖项,通常可以将该类的部分逻辑与这些依赖项结合起来,并将它们置于一个新的抽象之后:聚合服务。聚合服务不公开它的依赖关系,而只是公开一个方法,该方法允许访问提取的逻辑。

    如果你有一组被注入到多个服务中的依赖项,那么可以应用这种重构的一个很好的迹象就是。在你的情况下,你有一个明确的组由IDataAccess, IConverter, IConfigAccessITimeZoneAccess组成。您可以将两个,三个,甚至所有四个移动到聚合服务。

    让横切关注点与业务逻辑纠缠在一起是类变得太大、有太多依赖关系的另一个常见原因。您将经常看到事务处理、日志记录、审计跟踪、安全检查等与业务逻辑混在一起,并在整个代码库中重复。 解决这个问题的有效方法是将这些横切关注点移出包含业务逻辑的类,并使用拦截或修饰来应用它。如果你有一个坚实的设计,这是最有效的。例如,请阅读本文,了解如何应用横切关注点,而不必在整个代码库中进行全面更改。

    在项目中使用StructureMap IoC容器。让构造函数接受这些接口,并在项目中创建一个注册表,为每个接口设置要使用的类。

        public class DuckProjectRegistry : Registry
        {
            public DuckProjectRegistry()
            {
                For<IDataAccess >().Use<ConcreteClassDataAccess>();
                For<IConverter>().Use<ConcreteConverterXYZ>();
                For<IConfigAccess>().Use<ConcreteConfigAccess>().Singleton();
                // etc.
            }
        }
    public class Duck
    {
        private readonly IDataAccess _dataAccess;
        private readonly IConverter _converter;
        private readonly IConfigAccess _configAccess;
        // etc.
        public Duck(
            IDataAccess dataAccess,
            IConverter converter,
            IConfigAccess configAccess
            // ,etc.)
        {
            _dataAccess = dataAccess;
            _converter = converter;
            _configAccess = configAccess;
            // etc.
        }
    

    如果你不想要任何DI容器,对于帮助器,我建议你使用我所说的"抽象接口"创建空接口:

    public interface IDateTimerHelper { }
    public interface IFixedCalculationsHelper { }
    
    然后在扩展类中实现 <>之前公共静态类DateTimerHelperExtension{HelpMeForDateTimering(这个IDateTimerHelper dth/*, params*/){//帮助;}public static void HelpMe(this IDateTimerHelper dth/*, params*/){//帮助;}}公共静态类FixedCalculationsHelperExtension{(这个IFixedCalculationsHelper//*, params*/){//在这里实现}公共静态void HelpMe(这个IFixedCalculationsHelper/*,参数*/){//在这里实现}}之前

    最后像这样使用

    <>之前公共类狗:ifixedcalculationhelper,IDateTimerHelper{public Dog(/*其他注射*/){//初始化}DoWork(){(这是IDateTimerHelper).HelpMe();(这是IFixedCalculationsHelper).HelpMe();this.HelpMeForDateTimering ();this.HelpMeForFixedCalculations ();}}