Pipeline模式與Factory+Provider模式的應用

jiulang發表於2021-06-18

前言

我正在寫FastGithub這個小麻雀專案,裡面主要涉及了Pipeline模式和Factory+Provider模式,這兩種設計模式,讓這個專案在"ip掃描"和"ip查詢"兩個核心功能上如魚得水,在此分享給大家。

Pipeline

Pipeline模式也叫管道模式或流水線模式。通過預先設定好的一系列的階段來處理輸入的資料,每個階段的輸出即是下一個階段的輸入,每個階段可以選擇是否繼續執行一下階段。

上下文物件

在實現上,我們把所需的所有資料封裝在上下文物件,每個階段可以共享到同一份上下文物件。

中介軟體

在實現上,我們把每個階段的處理封裝為中介軟體,一箇中介軟體可以訪問到上下文物件和下一個階段的處理物件,在執行時可以訪問或修改上下文物件的資料。

實現詳解

完整的Pipeline構建程式碼,詳見https://github.com/xljiulang/FastGithub/tree/master/FastGithub.Core

階段處理物件

/// <summary>
/// 表示所有中介軟體執行委託
/// </summary>
/// <typeparam name="TContext">中介軟體上下文型別</typeparam>
/// <param name="context">中介軟體上下文</param>
/// <returns></returns>
public delegate Task InvokeDelegate<TContext>(TContext context);

委託中介軟體

Func<InvokeDelegate<TContext>, InvokeDelegate<TContext>>為一個委託中介軟體,第一個InvokeDelegate<TContext>表示傳入的下一個處理階段,第二個InvokeDelegate<TContext>表示當前處理階段。

/// <summary>
/// 定義中介軟體管道建立者的介面
/// </summary>
/// <typeparam name="TContext">中介軟體上下文</typeparam>
public interface IPipelineBuilder<TContext>
{
    /// <summary>
    /// 使用中介軟體
    /// </summary>
    /// <param name="middleware">中介軟體</param>
    /// <returns></returns>
    IPipelineBuilder<TContext> Use(Func<InvokeDelegate<TContext>, InvokeDelegate<TContext>> middleware);

    /// <summary>
    /// 建立所有中介軟體執行處理者
    /// </summary>
    /// <returns></returns>
    InvokeDelegate<TContext> Build();
}

強型別中介軟體

我們可以把委託中介軟體,轉換為如下的強型別中介軟體,InvokeAsync方法是本處理階段,next引數,是委託中介軟體的下個階段包裝。

/// <summary>
/// 定義中介軟體的介面
/// </summary>
/// <typeparam name="TContext"></typeparam>
public interface IMiddleware<TContext>
{
    /// <summary>
    /// 執行中介軟體
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個中介軟體</param>
    /// <returns></returns>
    Task InvokeAsync(TContext context, Func<Task> next);
}

使用詳解

掃描任務分為完整掃描和歷史結果掃描,使用的中介軟體有點差異,但都是把需要的中介軟體串起來就可以了。

/// <summary>
/// github掃描服務
/// </summary>
/// <param name="domainAddressFactory"></param>
/// <param name="scanResults"></param>
/// <param name="appService"></param>
/// <param name="logger"></param>
public GithubScanService(
    DomainAddressFacotry domainAddressFactory,
    GithubContextCollection scanResults,
    IServiceProvider appService,
    ILogger<GithubScanService> logger)
{
    this.domainAddressFactory = domainAddressFactory;
    this.scanResults = scanResults;
    this.logger = logger;

    this.fullScanDelegate = new PipelineBuilder<GithubContext>(appService, ctx => Task.CompletedTask)
        .Use<ConcurrentMiddleware>()
        .Use<StatisticsMiddleware>()
        .Use<TcpScanMiddleware>()
        .Use<HttpsScanMiddleware>()
        .Build();

    this.resultScanDelegate = new PipelineBuilder<GithubContext>(appService, ctx => Task.CompletedTask)
        .Use<StatisticsMiddleware>()
        .Use<HttpsScanMiddleware>()
        .Build();
}

Factory+Provider

多個Provider可以使用不同手段獲取到github的ip,Factory再把各Provider得到的ip進行整合,他們都是得到相同的功能:拿到github的ip,只是各個Provider才是具體幹活的,而且Provider之間沒有任何有關係。

IDomainAddressProvider

/// <summary>
/// 定義域名的ip提值者
/// </summary>
interface IDomainAddressProvider
{
    /// <summary>
    /// 建立域名與ip的關係
    /// </summary>
    /// <returns></returns>
    Task<IEnumerable<DomainAddress>> CreateDomainAddressesAsync();
}

DomainAddressFacotry

/// <summary>
/// 域名與ip關係工廠
/// </summary>
[Service(ServiceLifetime.Singleton)]
sealed class DomainAddressFacotry
{
    private readonly IEnumerable<IDomainAddressProvider> providers;

    /// <summary>
    /// 域名與ip關係工廠
    /// </summary>
    /// <param name="providers"></param>
    public DomainAddressFacotry(IEnumerable<IDomainAddressProvider> providers)
    {
        this.providers = providers;
    }

    /// <summary>
    /// 建立域名與ip的關係
    /// </summary>
    /// <returns></returns>
    public async Task<IEnumerable<DomainAddress>> CreateDomainAddressesAsync()
    {
        var hashSet = new HashSet<DomainAddress>();
        foreach (var provider in this.providers)
        {
            var domainAddresses = await provider.CreateDomainAddressesAsync();
            foreach (var item in domainAddresses)
            {
                hashSet.Add(item);
            }
        }
        return hashSet;
    }
}

模式優勢分析

FastGithub同時使用了上述兩種模式,其工作流程很簡單:使用DomainAddressFacotry建立要掃描的ip,然後使用pipeline建立得到的掃描委託進行掃描即可。想得到更多的ip,增加一個DomainAddressProvider即可,不影響到其它任何程式碼流程,想在掃描過程中做其它掃描邏輯,增加一個掃描中介軟體並安裝到合適位置即可。

相關文章