摘要的参数验证 class

Argument validation of abstract class

我有以下摘要class:

public abstract class FileClient {
     public abstract File GetFile(string Path);
     public abstract Task<File> GetFileAsync(string Path);
     public abstract void MoveFile(string Source, string Destination);
}

我还有多个派生的 classes 提供方法的实现。由于这些方法被声明为 public,因此这些 class 中的每一个都必须执行参数的参数验证。例如,两个 GetFile 方法的方法验证代码如下所示:

if (!IsConnected) {
    throw new NotConnectedException();
}

if (Path == null) {
    throw new ArgumentNullException(nameof(Path));
}

对所有派生的 classes 执行上述参数验证的最佳方法是什么?我个人能想到的有以下几种方式,欢迎大家补充。

1。在每个派生的 class

中执行验证

我个人不喜欢这个解决方案,因为代码重复。

2。实施验证方法

在实用程序 class 或抽象 class 本身中实施验证方法,并在每个派生的 class 中调用这些验证方法。我不喜欢这个,因为我需要为每个要验证的现有方法使用一个新方法。此外,这并没有解决代码重复问题,因为我仍然需要多次调用这些方法。

3。模板方法设计模式

通过在抽象方法中添加验证来实现 template method design 模式 class,并定义派生方法将覆盖的新抽象方法。

public File GetFile(string Path) {
    ...Validation
    return DoGetFile(Path);
}
protected abstract File DoGetFile(string Path);

这是我找到的最好的方法,但我不喜欢我有重复的方法名称(除了 Do 后缀)和描述。

4。将抽象方法设为虚拟

使方法虚拟化并在抽象中实现验证逻辑class。派生 classes 将需要先调用基方法,然后再执行自己的逻辑。

public abstract class FileClient {
     public virtual File GetFile(string Path) {
          ...Validation
          return null;
     }
}

public class FtpClient : FileClient {
     public override File GetFile(string Path) {
          File file = base.GetFile(Path);
          if (file != null) {
              return file;
          }
          ...Logic
     }
}

这种方法很干净,但我不喜欢它,原因如下:

  1. 方法被声明为虚拟仅用于验证。没有逻辑,因为它们总是 return null.
  2. 派生的 classes 将不知道如何处理由基础 class 编辑的结果 return。
  3. Base class 需要 return 异步方法的非空 Task 对象,它需要 "awaited" 派生 classes。从性能的角度来看,这可能重要也可能无关紧要,具体取决于要求。

还有第五种方法使用 code contracts

使用代码契约,您可以使用接口契约实现 design by contract,其中整个契约将在编译时自动注入到整个接口的所有实现中(实际上使用 post-编译过程)。

你只需要定义一个IFileClient接口和一个契约class。这可能是最优雅、最强大的解决方案。

参见以下代码示例:

[ContractClass(typeof(IFileClientContract))]
public interface IFileClient
{
     File GetFile(string path);
     Task<File> GetFileAsync(string path);
     void MoveFile(string source, string destination);
}

[ContractClassFor(typeof(IFileClient))]
public abstract class IFileClientContract : IFileClient
{
     public File GetFile(string Path)
     {
          Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(path));

          throw new NotImplementedException();
     }

     public Task<File> GetFileAsync(string path)
     {
          Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(path));

          throw new NotImplementedException();
     }


     public void MoveFile(string source, string destination)
     {
          Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(source));
          Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(destination));

          throw new NotImplementedException();
     }
}

// Any implementation of IFileClient will need to fulfill 
// its contracts, including derived classes of FileClient!
public abstract class FileClient : IFileClient
{
     public abstract File GetFile(string Path);
     public abstract Task<File> GetFileAsync(string Path);
     public abstract void MoveFile(string Source, string Destination);
}