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

时间:2015-05-21 18:11:24

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

我正在一个项目中工作,从不同的来源(例如数据库,3个外部网络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值方法等。我决定创建一些静态类,将此功能划分为特定的类,如DatetimeHelper,FixedCalculationsHelper,StringHandlingHelper等。

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

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

    public Dog(IDataAccess dataAccess,IConverter converter,IConfigAccess configAccess,     ITimezoneAccess timezoneAccess,IStringHandling stringHandler,     IDatetimeHelper datetimeHelper ... etc ...

  2. 处理此方案的最优雅/最佳方式是什么? (不确定这里是否使用了某些技术,如容器或类似的东西)

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

4 个答案:

答案 0 :(得分:2)

使用接口的目的是编码抽象而不是删除依赖关系的具体问题。

  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. 使用Interfaces可以更轻松地更改实现。它使您可以更好地控制程序的结构。您希望您的课程对扩展开放,但仍然关闭修改,即Open Closed Principle。在我看来,我会制作助手扩展方法,并放弃为它们制作接口。

答案 1 :(得分:1)

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

E.g。

public interface IDateTimerHelper { }
public interface IFixedCalculationsHelper { }

答案 2 :(得分:0)

如果您不想要任何DI容器,对于帮助者,我建议您利用我用来称为“抽象接口”的东西 创建空接口:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title></title>
    <script src="jquery.js"></script>
    <script src="jquery.canvasjs.js" ></script>
    <script src="canvasjs.js"></script>
    <script type="text/javascript" src="canvasjs.min.js"></script>


    <script type="text/javascript">
        $(document).ready(function () {

                $.getJSON("data.php", function (result) {

                var dataPoints = [];

                for (var i = 0; i <= result.length - 1; i++) {
                dataPoints.push({ x: Number(result[i].x), y: Number(result[i].y) });
                }

                var chart = new CanvasJS.Chart("chartContainer", {
                data: [
                {
                dataPoints: dataPoints
                }
                ]
                });

                chart.render();
                });
        });
    </script>
</head>
<body>

    <div id="chartContainer" style="width: 800px; height: 380px;"></div>

</body>
</html>

然后在扩展类中实现


    public static class DateTimerHelperExtension
    {
        public static void HelpMeForDateTimering(this IDateTimerHelper dth/*, params*/)
        {
            //Help here;
        }
        public static void HelpMe(this IDateTimerHelper dth/*, params*/)
        {
            //Help here;
        }
    }
    public static class FixedCalculationsHelperExtension
    {
        public static void HelpMeForFixedCalculations(this IFixedCalculationsHelper fch/*, params*/)
        {
            //implement here
        }
        public static void HelpMe(this IFixedCalculationsHelper fch/*, params*/)
        {
            //implement here
        }
    }

最后像这样使用


    public class Dog:IFixedCalculationsHelper,IDateTimerHelper
    {
        public Dog(/*other injections*/)
        {
            //Initialize
        }
        public void DoWork()
        {
            (this as IDateTimerHelper).HelpMe();
            (this as IFixedCalculationsHelper).HelpMe();
            this.HelpMeForDateTimering();
            this.HelpMeForFixedCalculations();
        }
    }

答案 3 :(得分:0)

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

但是,对于课程所依赖的每个依赖项,您都不需要这些特性。对于简单的帮助方法,它们不具有任何依赖性,从不必更换,修饰或截获,并且不会使测试复杂化,因此几乎不需要将它们提升为完整组件并隐藏他们背后的抽象。您很快就会看到您想要单独测试使用类,但需要使用真正的帮助程序逻辑。现在,您将无法在单元测试中对此进行布线。

我的建议是不要过分。

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

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

Refactoring to Aggregate Services是其中一种做法。如果一个类有很多依赖项,那么通常可以将该类的逻辑的额外部分与这些依赖项放在一起,并将它们置于一个新的抽象之后:聚合服务。该聚合服务不会公开其依赖项,而只是公开一种允许访问该提取逻辑的方法。

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

让跨领域的问题与业务逻辑纠缠在一起是另一个常见的原因,因为类太多了,依赖性太多了。您经常会看到事务处理,日志记录,审计跟踪,安全检查等与业务逻辑混淆,并在整个代码库中重复。

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