我正在一个项目中工作,从不同的来源(例如数据库,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等。
我建议避免使用这些静态类并将它们转换为带有接口的策略,并将它们作为其他外部数据访问接口传递给构造函数。
当我应用它时,我的类的构造函数将有很多接口参数,例如:
public Dog(IDataAccess dataAccess,IConverter converter,IConfigAccess configAccess, ITimezoneAccess timezoneAccess,IStringHandling stringHandler, IDatetimeHelper datetimeHelper ... etc ...
处理此方案的最优雅/最佳方式是什么? (不确定这里是否使用了某些技术,如容器或类似的东西)
非常欢迎任何评论或解释。提前谢谢。
答案 0 :(得分:2)
使用接口的目的是编码抽象而不是删除依赖关系的具体问题。
将许多接口传递给构造函数是可以的,但是您不希望传递具体的类。如果你不想让构造函数有参数,你可以使用setter注入而不是构造函数注入。
public class Duck
{
IDataAccess DataAccess { get; set; }
IConverter Converter { get; set; }
IConfigAccess ConfigAccess { get; set; }
ITimezoneAccess TimezoneAccess { get; set; }
public Duck()
{
// parameterless contructor
}
}
使用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是其中一种做法。如果一个类有很多依赖项,那么通常可以将该类的逻辑的额外部分与这些依赖项放在一起,并将它们置于一个新的抽象之后:聚合服务。该聚合服务不会公开其依赖项,而只是公开一种允许访问该提取逻辑的方法。
能够应用此重构的一个很好的迹象是,如果您有一组依赖项被注入到多个服务中。在您的情况下,您有一个由IDataAccess
,IConverter
,IConfigAccess
和ITimeZoneAccess
组成的清晰组。您可以将两个,三个或甚至全部四个移动到聚合服务。
让跨领域的问题与业务逻辑纠缠在一起是另一个常见的原因,因为类太多了,依赖性太多了。您经常会看到事务处理,日志记录,审计跟踪,安全检查等与业务逻辑混淆,并在整个代码库中重复。
解决此问题的有效方法是将这些横切关注点移出包含业务逻辑的类,并使用拦截或装饰来应用它。如果您使用SOLID设计,则效果最佳。例如,请查看this article,了解如何应用横切关注点,而无需在整个代码库中进行彻底更改。