首页 > 解决方案 > 将所有基本调用包装在派生类型中

问题描述

所以我有几个实例我希望能够做到这一点,但基本上我希望能够将所有对超类的调用包装在派生类型中。现在我正在尝试将所有对基本方法的调用包装在一个 Impersonator 中,但我也可以看到它的其他用途。

一个例子是

public void CopyFile(string filePath, string destPath)
{
  using(var I = new Impersonator("user", ".", "password"))
  {
     base.CopyFile(string filePath, string destPath);
  }
}

另一个方便的用途可能是

public void CopyFile(string filePath, string destPath)
{
  try
  {
    base.CopyFile(string filePath, string destPath);
  } catch(Exception e)
  {
    Log(e.Message);
  }
}

现在我想类似地包装所有基本调用。有没有一种方便的方法可以做到这一点,还是我必须手动包装所有这些?

我正在寻找类似“超类中的 foreach baseMethod 执行此操作”之类的东西

也许找到某种方法来捕获类的传入调用并将它们包装为一个动作?

public void ActionWrapper(Action action)
{
  try
  {
    action.Invoke();
  } catch(Exception e)
  {
    Log(e.Message);
  }
}

但是我怎么能以这种方式接听班级的电话呢?

老实说,这只是为了使类更易于维护并减少代码膨胀。我对这些或任何其他方法持开放态度。

标签: c#

解决方案


首先,我想赞扬你以这种方式解构代码的直觉。将错误处理/日志记录和安全/身份等问题与业务逻辑分开可以为可维护性创造奇迹。

您所描述的被称为装饰拦截。Mark Seemann 有一篇很好的博客文章,比较了日志记录上下文中的两种方法。

在不使用外部工具(如 DI 或 AOP 框架)的情况下,我认为ActionWrapper您提出的方法是一个好的开始。我对其进行了修改以显示模拟而不是日志记录,因为我认为模拟是一个更有趣的用例:

public void ActionWrapper(Action action)
{
  using(var I = new Impersonator("user", ".", "password"))
  {
    action.Invoke();
  }
}

所以问题是:如何有效地应用这种方法?

假设您现有的课程是:

public class FileCopier
{
    public void CopyFile(string filePath, string destPath)
    {
        // Do stuff
    }
}

正如您所建议的,您可以创建一个派生类来添加模拟:

public class FileCopierWithImpersonation : FileCopier
{
    public void CopyFile(string filePath, string destPath)
        => WithImpersonation(base.CopyFile(filePath, destPath));

    public void WithImpersonation(Action action)
    {
      using(var I = new Impersonator("user", ".", "password"))
      {
        action.Invoke();
      }
    }
}

在这里,FileCopierWithImpersonation作为装饰器FileCopier,通过继承实现。该WithImpersonation方法用作拦截器,可以在任何方法上应用模拟范围。

这应该足够好,但它会在实施中产生一些妥协。基类的方法都需要标记为virtual. 子类的构造函数可能需要将参数传递给基类。独立于基类的逻辑对子类的逻辑进行单元测试是不可能的。

因此,您可能想要提取一个接口 ( IFileCopier) 并使用组合而不是继承来应用装饰器:

public class FileCopierWithImpersonation : IFileCopier
{
    private readonly IFileCopier _decoratee;

    public FileCopierWithImpersonation(IFileCopier decoratee)
    {
         // If you don't want to inject the dependency, you could also instantiate
         // it here: _decoratee = new FileCopier();
        _decoratee = decoratee;
    }

    public void CopyFile(string filePath, string destPath)
        => WithImpersonation(_decoratee.CopyFile(filePath, destPath));

    public void WithImpersonation(Action action)
    {
      using(var I = new Impersonator("user", ".", "password"))
      {
        action.Invoke();
      }
    }
}

如果您使用的是 Visual Studio 2019,则有一个“通过...实现接口”的重构选项,它将通过调用相同类型的依赖项的方法来自动实现接口。之后,只需简单的查找/替换即可添加拦截器。

Visual Studio 2019 的动画

您还可以查看代码生成工具,例如自动生成装饰器的T4 模板。但请注意,.NET Core 不支持 T4。在这一点上,它看起来是一项遗留技术。


推荐阅读