詳解)
C# 管道模式Pipeline Pattern詳解一、核心思想你列出的六點(diǎn)本質(zhì)上是責(zé)任鏈模式 組合模式 依賴(lài)注入的組合應(yīng)用。核心就一句話把流程從一段又長(zhǎng)又硬編碼的方法拆解成一組可插拔的 Step由管道按順序編排執(zhí)行。傳統(tǒng)寫(xiě)法 管道寫(xiě)法 DoEverything() Pipeline { ├── Step A stepA(); ├── Step B stepB(); ├── Step C stepC(); └── Step D }二、為什么需要它對(duì)比痛點(diǎn)? 傳統(tǒng)寫(xiě)法的痛點(diǎn)publicclassOrderService{publicvoidPlaceOrder(Orderorder){// 1. 校驗(yàn)if(order.Items.Count0)thrownewException(空訂單);// 2. 計(jì)算價(jià)格order.Totalorder.Items.Sum(ii.Price*i.Qty);// 3. 庫(kù)存扣減_inventory.Deduct(order);// 4. 支付_payment.Charge(order);// 5. 發(fā)通知_email.Send(order);// 6. 記錄日志_log.Info(下單成功);// 7. 后續(xù)還要加風(fēng)控、優(yōu)惠券、積分...}}問(wèn)題一個(gè)方法越來(lái)越長(zhǎng)可讀性差加/刪步驟要改這個(gè)方法違反開(kāi)閉原則沒(méi)法單獨(dú)測(cè)庫(kù)存扣減這一步難測(cè)試其他業(yè)務(wù)想復(fù)用發(fā)通知邏輯只能復(fù)制粘貼難復(fù)用三、核心抽象1. 上下文Context—— 在 Step 之間傳遞數(shù)據(jù)publicclassOrderContext{publicOrderOrder{get;set;}new();publicboolIsValid{get;set;}publicdecimalDiscount{get;set;}publicListstringLogs{get;}new();}2. Step 接口 —— 每個(gè)步驟只做一件事publicinterfaceIStepTContext{TaskExecuteAsync(TContextcontext,CancellationTokenctdefault);}如果步驟需要決定是否繼續(xù)往下走可以返回一個(gè)結(jié)果publicenumStepResult{Continue,Stop}publicinterfaceIStepTContext{TaskStepResultExecuteAsync(TContextcontext,CancellationTokenctdefault);}3. Pipeline —— 負(fù)責(zé)按順序編排publicinterfaceIPipelineTContext{TaskExecuteAsync(TContextcontext,CancellationTokenctdefault);}publicclassPipelineTContext:IPipelineTContext{privatereadonlyIEnumerableIStepTContext_steps;publicPipeline(IEnumerableIStepTContextsteps){_stepssteps;}publicasyncTaskExecuteAsync(TContextcontext,CancellationTokenctdefault){foreach(varstepin_steps){varresultawaitstep.ExecuteAsync(context,ct);if(resultStepResult.Stop)break;}}}四、完整示例訂單處理管道Step 1 — 校驗(yàn)publicclassValidateOrderStep:IStepOrderContext{publicTaskStepResultExecuteAsync(OrderContextctx,CancellationTokenctdefault){if(ctx.Order.Items.Count0){ctx.IsValidfalse;ctx.Logs.Add(訂單為空終止流程);returnTask.FromResult(StepResult.Stop);}ctx.IsValidtrue;ctx.Logs.Add(校驗(yàn)通過(guò));returnTask.FromResult(StepResult.Continue);}}Step 2 — 計(jì)算價(jià)格publicclassCalculatePriceStep:IStepOrderContext{publicTaskStepResultExecuteAsync(OrderContextctx,CancellationTokenctdefault){ctx.Order.Totalctx.Order.Items.Sum(ii.Price*i.Quantity)-ctx.Discount;ctx.Logs.Add($價(jià)格計(jì)算完成:{ctx.Order.Total});returnTask.FromResult(StepResult.Continue);}}Step 3 — 扣庫(kù)存publicclassDeductInventoryStep:IStepOrderContext{privatereadonlyIInventoryService_inventory;publicDeductInventoryStep(IInventoryServiceinventory)_inventoryinventory;publicasyncTaskStepResultExecuteAsync(OrderContextctx,CancellationTokenctdefault){varokawait_inventory.DeductAsync(ctx.Order,ct);if(!ok){ctx.Logs.Add(庫(kù)存不足終止流程);returnStepResult.Stop;}ctx.Logs.Add(庫(kù)存扣減成功);returnStepResult.Continue;}}Step 4 — 支付publicclassPaymentStep:IStepOrderContext{privatereadonlyIPaymentService_payment;publicPaymentStep(IPaymentServicepayment)_paymentpayment;publicasyncTaskStepResultExecuteAsync(OrderContextctx,CancellationTokenctdefault){await_payment.ChargeAsync(ctx.Order,ct);ctx.Logs.Add(支付成功);returnStepResult.Continue;}}Step 5 — 通知publicclassNotifyStep:IStepOrderContext{privatereadonlyINotifier_notifier;publicNotifyStep(INotifiernotifier)_notifiernotifier;publicasyncTaskStepResultExecuteAsync(OrderContextctx,CancellationTokenctdefault){await_notifier.SendAsync(ctx.Order,ct);ctx.Logs.Add(通知已發(fā)送);returnStepResult.Continue;}}五、編排從硬編碼到可配置方式 ADI 容器自動(dòng)裝配推薦// Program.csbuilder.Services.AddScopedIStepOrderContext,ValidateOrderStep();builder.Services.AddScopedIStepOrderContext,CalculatePriceStep();builder.Services.AddScopedIStepOrderContext,DeductInventoryStep();builder.Services.AddScopedIStepOrderContext,PaymentStep();builder.Services.AddScopedIStepOrderContext,NotifyStep();builder.Services.AddScopedIPipelineOrderContext,PipelineOrderContext();調(diào)用publicclassOrderAppService{privatereadonlyIPipelineOrderContext_pipeline;publicOrderAppService(IPipelineOrderContextpipeline)_pipelinepipeline;publicasyncTaskPlaceOrderAsync(Orderorder){varctxnewOrderContext{Orderorder};await_pipeline.ExecuteAsync(ctx);}}??IEnumerableIStepT的注入順序取決于注冊(cè)順序。想嚴(yán)格控制順序看方式 B。方式 B顯式命名 排序更可控publicinterfaceIStepTContext{stringName{get;}intOrder{get;}TaskStepResultExecuteAsync(TContextcontext,CancellationTokenctdefault);}在Pipeline中排序_stepssteps.OrderBy(ss.Order).ToList();方式 C從配置/數(shù)據(jù)庫(kù)動(dòng)態(tài)生成管道高級(jí)// pipeline.json{steps:[ValidateOrderStep,CalculatePriceStep,DeductInventoryStep,PaymentStep,NotifyStep]}publicclassConfigurablePipelineTContext:IPipelineTContext{privatereadonlyIServiceProvider_sp;privatereadonlyListType_stepTypes;publicConfigurablePipeline(IServiceProvidersp,IConfigurationcfg){_spsp;_stepTypescfg.GetSection(pipeline:steps).GetListstring().Select(nType.GetType($MyApp.Steps.{n})!).ToList();}publicasyncTaskExecuteAsync(TContextctx,CancellationTokenctdefault){foreach(vartypein_stepTypes){varstep(IStepTContext)_sp.GetRequiredService(type);if(awaitstep.ExecuteAsync(ctx,ct)StepResult.Stop)break;}}}這樣換個(gè) JSON 就換了流程不用重新編譯。六、為什么滿足你的 6 個(gè)特性特性如何滿足可讀性流程 IEnumerableIStep一眼看清單可維護(hù)新增步驟 新增一個(gè)類(lèi)不動(dòng)老代碼可測(cè)試每個(gè) Step 都是獨(dú)立類(lèi)輸入 Context 輸出 StepResult直接單測(cè)可復(fù)用NotifyStep可以同時(shí)被訂單、退款、注冊(cè)等管道復(fù)用可編排順序由 DI 注冊(cè)順序 / 配置 / 數(shù)據(jù)庫(kù)決定SOLID單一職責(zé)每 Step 一件事 開(kāi)閉原則擴(kuò)展加類(lèi)修改改配置單測(cè)示例xUnitpublicclassValidateOrderStepTests{[Fact]publicasyncTask 空訂單_應(yīng)終止流程(){varstepnewValidateOrderStep();varctxnewOrderContext{OrdernewOrder()};// 無(wú) itemsvarresultawaitstep.ExecuteAsync(ctx);Assert.Equal(StepResult.Stop,result);Assert.False(ctx.IsValid);}[Fact]publicasyncTask 正常訂單_應(yīng)繼續(xù)(){varstepnewValidateOrderStep();varctxnewOrderContext{OrdernewOrder{Items{newOrderItem{Quantity1}}}};varresultawaitstep.ExecuteAsync(ctx);Assert.Equal(StepResult.Continue,result);Assert.True(ctx.IsValid);}}完全不需要 mock 任何東西。七、進(jìn)階中間件風(fēng)格類(lèi)似 ASP.NET Core如果 Step 需要包裹下一個(gè) Step做超時(shí)、日志、事務(wù)可以用洋蔥模型publicdelegateTaskStepDelegateTContext(TContextctx);publicinterfaceIMiddlewareTContext{TaskInvokeAsync(TContextctx,StepDelegateTContextnext);}publicclassPipelineBuilderTContext{privatereadonlyListFuncStepDelegateTContext,StepDelegateTContext_componentsnew();publicPipelineBuilderTContextUse(FuncStepDelegateTContext,StepDelegateTContextmiddleware){_components.Add(middleware);returnthis;}publicPipelineBuilderTContextUseStepTS(IServiceProvidersp)whereTS:IStepTContext{returnUse(nextasyncctx{varstepsp.GetRequiredServiceTS();varrawaitstep.ExecuteAsync(ctx);if(rStepResult.Continue)awaitnext(ctx);});}publicStepDelegateTContextBuild(StepDelegateTContextterminal){varappterminal;for(inti_components.Count-1;i0;i--)app_components[i](app);returnapp;}}用法事務(wù) 日志 步驟varpipelinenewPipelineBuilderOrderContext().Use(nextasyncctx{usingvartx_db.BeginTransaction();awaitnext(ctx);awaittx.CommitAsync();}).Use(nextasyncctx{varswStopwatch.StartNew();awaitnext(ctx);_log.Info($耗時(shí){sw.ElapsedMilliseconds}ms);}).UseStepValidateOrderStep(sp).UseStepCalculatePriceStep(sp).UseStepPaymentStep(sp).Build(_Task.CompletedTask);awaitpipeline(ctx);八、什么時(shí)候不該用步驟永遠(yuǎn)只有 2~3 個(gè)且不會(huì)變→ 直接用普通方法別過(guò)度設(shè)計(jì)步驟間強(qiáng)耦合、需要頻繁互操作共享局部變量→ 管道反而更繞團(tuán)隊(duì)不熟悉這種模式→ 先用注釋清晰的順序方法等復(fù)雜了再重構(gòu)九、總結(jié)一句話Pipeline IEnumerableIStepTContext DI 排序 配置驅(qū)動(dòng)。每個(gè) Step 一個(gè)類(lèi)管道負(fù)責(zé)順序配置負(fù)責(zé)編排——這就是可讀、可維護(hù)、可測(cè)試、可復(fù)用、可編排、SOLID 的落地方式。需要我針對(duì)某個(gè)具體業(yè)務(wù)比如支付回調(diào)、審批流、數(shù)據(jù) ETL給出更貼合的管道設(shè)計(jì)嗎