5

我試圖將Specification pattern應用到我的驗證邏輯。但是我有一些異步驗證的問題。規範模式異步

比方說,我有一個實體AddRequest(有2個字符串屬性文件名和內容)需要驗證。

我需要創建3個驗證:

  1. 驗證,如果文件名不包含無效字符

  2. 驗證,如果內容是正確的

  3. 異步驗證是否與文件名的文件是存在在數據庫上。在這種情況下,我應該有一些像Task<bool> IsSatisfiedByAsync

但我怎麼能同時實現IsSatisfiedByIsSatisfiedByAsync?我應該創建2個接口,如ISpecificationIAsyncSpecification,或者我可以在一個接口中完成嗎?

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

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

AndSpecification

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爲檢查,如果我真的需要做異步?

例如我在這裏的驗證器(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如果我需要異步?

+2

如果你確實需要支持異步規範,那麼我推廣了異步和同步下降的合同。鑑於規範可以任意組合在一起,並且客戶端不知道規範是否是異步的,將所有內容視爲異步處理似乎更自然。 – plalx

+0

'request.FileName.Length> 1024'之前是否必須執行'string.IsNullOrEmpty(request.FileName)'?它不能是異步 – ByeBye

+0

@ByeBye你在說什麼? 'string.IsNullOrEmpty(request.FileName)'在'request.FileName.Length> 1024'之前執行'看代碼 –

回答

2

但我怎麼能同時實現IsSatisfiedBy和IsSatisfiedByAsync?我應該創建2個接口,如ISpecification和IAsyncSpecification,還是我可以在一個接口中完成?

您可以同時定義同步和異步接口,但任何通用組合實現都必須僅實現異步版本。

由於asynchronous methods on interfaces mean "this might be asynchronous"而同步方法意味着「這個必須是同步的」,我會與異步唯一接口去,因爲這樣的:

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

你也應該使用泛型方法

+0

_if第一個結果是假的,你有兩個或更多的異步檢查,這將是一個浪費<=「性能」可能意味着不同的事情;完成時間明顯優先於此(否則他會簡單地連續執行任何長期運行的規範評估)。當系統低於峯值使用時,一些額外的處理也不是真正的浪費,而是電力的浪費。另外,從兩個任務的最早完成返回的早期意味着另一個可以停止,避免浪費。事實上,如果兩個同步操作中的第一個操作成功,如果另一個操作失敗,則總是「浪費」。 – Iucounu

+0

可能有些情況下,例如一個詳細的綜合錯誤信息可能會有用 - 雖然我們用'Result'類型(隱式重載爲bool)而不是返回一個bool – Julian

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