【问题标题】:What is the best way to create Interfaces that are injected into constructor in C#? [closed]在 C# 中创建注入构造函数的接口的最佳方法是什么? [关闭]
【发布时间】:2015-08-03 13:38:24
【问题描述】:

我在一个项目中工作,该项目从不同来源获取外部数据,例如数据库、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)

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

在开发代码的时候,所有类之间都有一些通用的功能,比如日期时间操作、定值方法等。我决定创建一些静态类来把这个功能划分到具体的类中,比如DatetimeHelper、FixedCalculationsHelper、StringHandlingHelper等.

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

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

    public Dog(IDataAccess dataAccess, IConverter 转换器, IConfigAccess configAccess, ITimezoneAccess timezoneAccess, IStringHandling stringHandler, IDatetimeHelper datetimeHelper ...等...

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

  1. 为什么最好将此静态类转换为接口/实现策略(即使此方法是静态的,例如 CalculateArea(int value1, int value2))?

非常欢迎任何评论或解释。提前致谢。

【问题讨论】:

  • 您正在寻找Dependency Injection。有许多框架可以帮助您。
  • 告诉他使用 DI 并不能回答他的两个问题
  • 你可以改用setter注入。
  • 使用 IOC 容器怎么样?

标签: c# interface dependency-injection strategy-pattern


【解决方案1】:

使用接口的目的是编码抽象而不是删除依赖项的具体化。

  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. 使用接口更改实现会容易得多。它使您可以更好地控制程序的结构。你想让你的类对扩展开放但对修改关闭,这就是Open Closed Principle。在我看来,我会让助手扩展方法并放弃为它们制作接口。

【讨论】:

    【解决方案2】:

    在您的项目中使用 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.
        }
    

    【讨论】:

      【解决方案3】:

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

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

      我的建议是不要过度。

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

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

      Refactoring to Aggregate Services 是其中一种做法。如果一个类有许多依赖项,通常可以使用这些依赖项来增加该类的部分逻辑,并将它们放在一个新的抽象后面:您的聚合服务。该聚合服务不公开其依赖项,而只是公开一个允许访问提取的逻辑的方法。

      能够应用此重构的一个很好的迹象是,如果您有一组依赖项被注入到多个服务中。在您的情况下,您有一个由IDataAccessIConverterIConfigAccessITimeZoneAccess 组成的明确组。您可以将两个、三个甚至四个全部移动到聚合服务中。

      让横切关注点与业务逻辑纠缠在一起是类变得太大、依赖太多的另一个常见原因。您经常会看到事务处理、日志记录、审计跟踪、安全检查等与业务逻辑混在一起,并在整个代码库中重复出现。

      解决此问题的有效方法是将这些横切关注点移出包含业务逻辑的类,并使用拦截或修饰来应用它。如果您有 SOLID 设计,则此方法效果最佳。例如,查看this article,了解如何应用横切关注点,而无需在整个代码库中进行彻底的更改。

      【讨论】:

      • 非常感谢@Steven
      【解决方案4】:

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

      public interface IDateTimerHelper { }
      public interface IFixedCalculationsHelper { }
      

      然后在扩展类中实现

      公共静态类 DateTimerHelperExtension { public static void HelpMeForDateTimering(this IDateTimerHelper dth/*, params*/) { //这里有帮助; } public static void HelpMe(this IDateTimerHelper dth/*, params*/) { //这里有帮助; } } 公共静态类 FixedCalculationsHelperExtension { public static void HelpMeForFixedCalculations(this IFixedCalculationsHelper fch/*, params*/) { //这里实现 } public static void HelpMe(this IFixedCalculationsHelper fch/*, params*/) { //这里实现 } }

      终于这样用了

      公共类 Dog:IFixedCalculationsHelper,IDateTimerHelper { public Dog(/*其他注射*/) { //初始化 } 公共无效 DoWork() { (这作为 IDateTimerHelper).HelpMe(); (作为 IFixedCalculationsHelper).HelpMe(); this.HelpMeForDateTimering(); this.HelpMeForFixedCalculations(); } }

      【讨论】:

      • 这很有趣,我从未使用过带有扩展方法的接口。
      • 是的,很有趣!同样通过这种机制,您可以告别抽象类(存在继承限制问题)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-30
      • 1970-01-01
      • 1970-01-01
      • 2021-02-03
      • 2014-01-22
      • 2020-01-11
      • 1970-01-01
      相关资源
      最近更新 更多