抽象类的参数验证

时间:2016-08-26 20:35:43

标签: c# validation arguments abstract

我有以下抽象类:

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);
}

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

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

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

对所有派生类执行上述参数验证的最佳方法是什么?我个人可以想到以下几种方式,随意提及其他方法。

1。在每个派生类中执行验证

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

2。实施验证方法

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

3。模板方法设计模式

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

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

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

4。使抽象方法虚拟

使方法成为虚拟,并在抽象类中实现验证逻辑。派生类需要先调用基本方法,然后执行自己的逻辑。

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. 声明方法仅用于验证。没有逻辑,因为它们总是返回null。
  2. 派生类不知道如何处理基类返回的结果。
  3. 基类需要为异步方法返回一个非空的Task对象,这些方法需要被派生类“等待”。根据要求,从性能的角度来看,这可能也可能无关紧要。

1 个答案:

答案 0 :(得分:3)

使用code contracts的第五种方法。

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

您只需要定义IFileClient接口和合同类。这是最优雅和最强大的解决方案。

请参阅以下代码示例:

[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);
}