【问题标题】:Why doesn't the C# Standard Library (mscorlib) provide abstractions for its classes?为什么 C# 标准库 (mscorlib) 不为其类提供抽象?
【发布时间】:2017-05-31 05:21:16
【问题描述】:

C# 标准库目前不为其提供的大多数产品提供抽象/接口,例如FileHttpClient 等。(实际上,我不确定 VB.NET 是否属于这种情况, F# 等,所以现在让我们坚持使用 C#。)然而,需求非常高,因为单元测试实际上需要您模拟对 IO、网络等的依赖项。有很多自制解决方案,其中大部分“包装器。”

我很好奇的是,为什么标准库不能通过简单地将接口包含在库中来使这变得更容易。有设计原因吗?风格?

【问题讨论】:

  • 好吧,一方面File 是静态的..
  • 好吧,也许不是最好的例子。 System.IO.Stream 呢?另外,我认为即使像FileDirectory 这样的类是静态的,我认为我的观点仍然成立——IO 依赖项通常需要被隔离,但访问它们的标准库方式是静态的?跨度>
  • IO.Stream 不是抽象的吗?

标签: c# dependency-injection mocking abstraction


【解决方案1】:

很简单,当 Microsoft 首次构建 .NET 框架时,他们无法预料到依赖注入和单元测试的广泛使用。

但这是一个常见问题,不仅适用于本机 .NET 类型,也适用于 3rd 方库中的类型。处理没有抽象的类型的常用技术是使用adapter pattern 进行抽象。那么你只需要使用你的内置类型而不是库类型。

示例:UrlHelper

在 MVC 4 和之前的 UrlHelper 类没有虚拟成员,所以它不能被模拟或注入。要解决此问题,只需创建一个具有相同成员的接口(这可以使用 Visual Studio 中的“提取接口”功能轻松完成)...

public interface IUrlHelper
{
    string Action(string actionName);
    string Action(string actionName, object routeValues);
    string Action(string actionName, string controllerName);
    string Action(string actionName, string controllerName, object routeValues);
    string Action(string actionName, string controllerName, object routeValues, string protocol);
    string Action(string actionName, string controllerName, RouteValueDictionary routeValues);
    string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName);
    string Action(string actionName, RouteValueDictionary routeValues);
    string Content(string contentPath);
    string Encode(string url);
    RequestContext RequestContext { get; }
    RouteCollection RouteCollection { get; }
    string RouteUrl(object routeValues);
    string RouteUrl(string routeName);
    string RouteUrl(string routeName, object routeValues);
    string RouteUrl(string routeName, object routeValues, string protocol);
    string RouteUrl(string routeName, RouteValueDictionary routeValues);
    string RouteUrl(string routeName, RouteValueDictionary routeValues, string protocol, string hostName);
    string RouteUrl(RouteValueDictionary routeValues);
    bool IsLocalUrl(string url);
    string HttpRouteUrl(string routeName, object routeValues);
    string HttpRouteUrl(string routeName, RouteValueDictionary routeValues);
}

...然后创建一个继承UrlHelper并实现IUrlHelper的适配器类型...

public class UrlHelperAdapter : UrlHelper, IUrlHelper
{
    private UrlHelperAdapter(RequestContext requestContext)
        : base(requestContext)
    {
    }

    public UrlHelperAdapter(RequestContext requestContext, RouteCollection routeCollection)
        : base(requestContext, routeCollection)
    {
    }
}

完成此操作后,您可以将IUrlHelper 传递给任何方法或构造函数,并且只需将UrlHelperAdapter 作为具体类型传递(或者如果使用模拟框架,则构建IUrlHelper 的模拟)。

这种技术适用于大多数没有抽象的非静态类型。

正如您已经发现的那样,那里有整个库通过构建自己的适配器为您完成工作,如果可以节省一些步骤,使用它们并没有错。

如 cmets 中所述,IO.Stream 已经一个抽象,因此在特定情况下不需要。

【讨论】:

  • 最重要的是,Microsoft 在这些情况下必须提供的抽象将非常通用,以至于它们仍然不适合您的应用程序需求。这会阻碍应用程序的设计和可测试性。换句话说,您的应用程序仍然会大规模违反依赖倒置原则。相反,答案是(正如 NightOwl 已经非常正确地指出的那样)为您自己指定的抽象定义适配器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
  • 2017-06-01
  • 1970-01-01
  • 2018-06-21
  • 2013-08-06
  • 1970-01-01
相关资源
最近更新 更多