抽象类的参数验证

本文关键字:验证 参数 抽象类 | 更新日期: 2023-09-27 18:04:18

我有以下抽象类:

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

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

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

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

1。在每个派生类

中执行验证

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

2。实现验证方法

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

3。模板方法设计模式

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

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对象,这将需要被派生类"等待"。从性能的角度来看,这可能重要,也可能无关紧要,这取决于需求。

抽象类的参数验证

还有第五种方法使用代码契约

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

你只需要定义一个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);
}