7

我正在尝试将规范模式应用于我的验证逻辑。但是我在异步验证方面遇到了一些问题。

假设我有一个AddRequest需要验证的实体(具有 2 个字符串属性 FileName 和 Content)。

我需要创建 3 个验证器:

  1. 验证 FileName 是否不包含无效字符

  2. 验证内容是否正确

  3. 异步验证数据库中是否存在具有 FileName 的文件。在这种情况下,我应该有类似的东西Task<bool> IsSatisfiedByAsync

但是我怎样才能同时实现IsSatisfiedByandIsSatisfiedByAsync呢?我应该创建 2 个类似的接口ISpecificationIAsyncSpecification还是可以合二为一?

我的版本ISpecification(我只需要 And)

    public interface ISpecification
    {
        bool IsSatisfiedBy(object candidate);
        ISpecification And(ISpecification other);
    }

和规格

public class AndSpecification : CompositeSpecification 
{
    private ISpecification leftCondition;
    private ISpecification rightCondition;

    public AndSpecification(ISpecification left, ISpecification right) 
    {
        leftCondition = left;
        rightCondition = right;
    }

    public override bool IsSatisfiedBy(object o) 
    {
        return leftCondition.IsSatisfiedBy(o) && rightCondition.IsSatisfiedBy(o);
    }
}

要验证文件是否存在,我应该使用:

 await _fileStorage.FileExistsAsync(addRequest.FileName);

IsSatisfiedBy如果我真的需要异步执行该检查,我该如何编写?

例如这里我的 FileName 验证器 (1)

public class FileNameSpecification : CompositeSpecification 
{
    private static readonly char[] _invalidEndingCharacters = { '.', '/' };
    
    public override bool IsSatisfiedBy(object o) 
    {
        var request = (AddRequest)o;
        if (string.IsNullOrEmpty(request.FileName))
        {
            return false;
        }
        if (request.FileName.Length > 1024)
        {
            return false;
        }
        if (request.FileName.Contains('\\') || _invalidEndingCharacters.Contains(request.FileName.Last()))
        {
            return false;
        }

        return true
    }
}

我需要创建 FileExistsSpecification 并使用如下:

var validations = new FileNameSpecification().And(new FileExistsSpecification());
if(validations.IsSatisfiedBy(addRequest)) 
{ ... }

FileExistsSpecification但是如果我需要异步,我该如何创建?

4

3 回答 3

3

但是如何同时实现 IsSatisfiedBy 和 IsSatisfiedByAsync?我应该创建 2 个接口,例如 ISpecification 和 IAsyncSpecification,还是可以合二为一?

您可以定义同步和异步接口,但任何通用复合实现都必须只实现异步版本。

由于接口上的异步方法意味着“这可能是异步的”,而同步方法意味着“这必须是同步的”,所以我将使用仅异步接口,如下所示:

public interface ISpecification
{
  Task<bool> IsSatisfiedByAsync(object candidate);
}

如果您的许多规范是同步的,您可以使用基类来提供帮助:

public abstract class SynchronousSpecificationBase : ISpecification
{
  public virtual Task<bool> IsSatisfiedByAsync(object candidate)
  {
    return Task.FromResult(IsSatisfiedBy(candidate));
  }
  protected abstract bool IsSatisfiedBy(object candidate);
}

复合材料将是:

public class AndSpecification : ISpecification 
{
  ...

  public async Task<bool> IsSatisfiedByAsync(object o) 
  {
    return await leftCondition.IsSatisfiedByAsync(o) && await rightCondition.IsSatisfiedByAsync(o);
  }
}

public static class SpecificationExtensions
{
  public static ISpecification And(ISpeicification @this, ISpecification other) =>
      new AndSpecification(@this, other);
}

和个别规格,如:

public class FileExistsSpecification : ISpecification
{
  public async Task<bool> IsSatisfiedByAsync(object o)
  {
    return await _fileStorage.FileExistsAsync(addRequest.FileName);
  }
}

public class FileNameSpecification : SynchronousSpecification 
{
  private static readonly char[] _invalidEndingCharacters = { '.', '/' };

  public override bool IsSatisfiedBy(object o) 
  {
    var request = (AddRequest)o;
    if (string.IsNullOrEmpty(request.FileName))
      return false;
    if (request.FileName.Length > 1024)
      return false;
    if (request.FileName.Contains('\\') || _invalidEndingCharacters.Contains(request.FileName.Last()))
      return false;
    return true;
  }
}

用法:

var validations = new FileNameSpecification().And(new FileExistsSpecification());
if (await validations.IsSatisfiedByAsync(addRequest))
{ ... }
于 2017-08-14T20:39:56.480 回答
-1

我不知道,为什么您需要以同步驱动模式进行异步操作。

想象一下,如果第一个结果是false并且您有两个或多个异步检查,那将是对性能的浪费。

如果您想知道如何使异步请求重新同步,可以尝试使用以下方法:

public class FileExistsSpecification : CompositeSpecification
{
    public override bool IsSatisfiedBy(object o)
    {
        var addRequest = (AddRequest)o
        Task<bool> fileExistsResult = _fileStorage.FileExistsAsync(addRequest.FileName);
        fileExistsResult.Wait();

        return fileExistsResult.Result;
    }
}

您还应该使用泛型方法。

于 2017-08-14T15:01:54.093 回答
-1

我认为您的主要目标是确保代码尽快完成以评估复合规范,在执行子规范时,一个或多个可能需要一段时间,是吗?在模式实现之外调用代码来异步调用规范总是可能的;在这一点上,这并不是你真正关心的问题。

那么,鉴于此,给您的 ISpecification 一个额外的属性怎么样?

public interface ISpecification 
{
    bool IsAsynchronous { get; }
    bool IsSatisfiedBy(object o);
}

然后,对于非复合同步或异步类型规范,硬编码 IsAsynchronous 的返回值。但是在复合的情况下,以孩子为基础,即:

public class AndSpecification : ISpecification
{
    private ISpecification left;
    private ISpecification right;

    public AndSpecification(ISpecification _left, ISpecification _right) 
    {
        if (_left == null || _right == null) throw new ArgumentNullException();        
        left = _left;
        right = _right;
    }

    public bool IsAsynchronous { get { return left.IsAsynchronous || right.IsAsynchronous; }

    public override bool IsSatisfiedBy(object o) 
    {
        if (!this.IsAsynchronous)            
            return leftCondition.IsSatisfiedBy(o) && rightCondition.IsSatisfiedBy(o);

        Parallel.Invoke(
            () => {
                if (!left.IsSatisfiedBy(o)) return false;
            },
            () => {
                if (!right.IsSatisfiedBy(o)) return false;
            }
        );

        return true;
    }
}

但更进一步,您不想浪费性能。因此,当有一个同步和一个异步时,为什么不首先评估快速、同步的子节点呢?这是基本思想的一个接近完成的版本:

public class AndSpecification : ISpecification
{
    private ISpecification left;
    private ISpecification right;

    public AndSpecification(ISpecification _left, ISpecification _right) 
    {
        if (_left == null || _right == null) throw new ArgumentNullException();        
        left = _left;
        right = _right;
    }

    public bool IsAsynchronous { get { return left.IsAsynchronous || right.IsAsynchronous; }

    public override bool IsSatisfiedBy(object o) 
    {
        if (!left.IsAsynchronous) 
        {
            if (!right.IsAsynchronous) 
            {
                return left.IsSatisfiedBy(o) && right.IsSatisfiedBy(o);
            }
            else
            {
                if (!left.IsSatisfiedBy(o)) return false;
                return right.IsSatisfiedBy(o);
            } 
        }
        else if (!right.IsAsynchronous) 
        {
            if (!right.IsSatisfiedBy(o)) return false;
            return left.IsSatisfiedBy(o);
        }
        else
        {
            Parallel.Invoke(
                () => {
                    if (!left.IsSatisfiedBy(o)) return false;
                },
                () => {
                    if (!right.IsSatisfiedBy(o)) return false;
                }
            );

            return true;
        }
    }
}
于 2017-08-14T15:13:56.210 回答