我画着图,FluentAPI 她自己就生成了
在 Newbe.ObjectVistor 0.3 版本中我們非常興奮的引入了一個緊張刺激的新特性:使用狀態圖來生成任意給定的 FluentAPI 設計。
開篇摘要
在非常多優秀的框架中都存在一部分 FluentAPI 的設計。這種 API 設計更加符合人類自言語言描述。使得代碼更加具備可讀性。
在 Newbe.ObjectVistor 0.3 版本中,我們設計引入了一種使用狀態圖來自動生成 FluentAPI 代碼的機制。極大了簡化了 FluentAPI 實現所需要的腦力勞動。
本篇我們將通過一些示例,來了解一下當前版本中該特性的主要效果。
整數累加 FluentAPI
假如,我們現在需要實現下面這樣效果的一個 API:
[Test] public?void?SumList() {var?sumBuilder?=?new?SumBuilder(new?List<int>());var?re?=?sumBuilder.AddNumber(1).AddNumber(2).AddNumber(3).Sum();re.Should().Be(6); }這個 API 使用 FluentAPI 的方式來表述一個累加的過程。
為了實現這個 API 設計,在 Newbe.ObjectVisitor 0.3 中,使用下面這樣一個狀態圖標記表述這個 API 設計:
stateDiagram[*] --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : AddNumber(int number)AddNumber --> [*] : Sum() return int這實際上是 mermaid 狀態圖標記。轉換為圖形即為下面這個效果。不需要過多的解釋就可以理解:
SumBuilder有了這個狀態圖之后,使用 Newbe.ObjectVisitor 中的 FluentApiDesignParser 和 FluentApiFileGenerator 便可以生成如下代碼。
using?System; using?System.Collections.Generic; using?System.Linq;namespace?Newbe.ObjectVisitor.Tests.SumBuilderFluentApi {public?class?SumBuilder?:?Newbe.ObjectVisitor.IFluentApi,?SumBuilder.ISumBuilder_AddNumber{private?readonly?List<int>?_context;public?SumBuilder(List<int>?context){_context?=?context;}#region?UserImplprivate?void?Core_AddNumber(int?number){throw?new?NotImplementedException();}private?int?Core_Sum(){throw?new?NotImplementedException();}#endregion#region?AutoGenerate ///?此處省略了自動生成的固定代碼部分,請到倉庫中查看#endregion} }有了這個模板之后,只要實現 Core_AddNumber 和 Core_Sum,一個符合預期設計的 FluentAPI 就完成了!
累加后累乘
現在,我們稍微改變一下需求。上節我們實現的是一個 1+2+3 這樣的累加效果。現在我們需要一個 (1+2+3)*(4+5+6)*(7+8+9+10) 這樣的效果。
示例的調用代碼如下:
[Test] public?void?MultipleSumList() {var?builder?=?new?MultipleSumBuilder(new?List<List<int>>());var?re?=?builder.AddNumber(1).AddNumber(2).NextFactor().AddNumber(3).Sum();re.Should().Be(9); }為了實現這個效果,我們修改一下狀態圖,增加一條新的規則,得到:
stateDiagram[*] --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : NextFactor()AddNumber --> [*] : Sum() return int如圖:
MultipleSumBuilder創建數據庫鏈接字符串
前面的示例或許缺乏生產實際,現在添加一個生產示例。我們現在要實現一個 ConnectionStringBuilder 用來創建數據庫連接字符串,其中有以下限制:
必須指定 Host。
身份認證方式必須且只能指定一種,要么是用戶名密碼方式,要么是 Windows 憑據。
首先,我們有一個模型來保存上面提到的數據。
public?class?ConnectionStringModel {public?string?Host?{?get;?set;?}public?string?Username?{?get;?set;?}public?string?Password?{?get;?set;?}public?bool??IsWindowsAuthentication?{?get;?set;?} }接著,我們直接使用狀態圖來設計這個 FluentAPI。設計結果如下:
stateDiagram[*] --> SetHost : SetHost(string host)SetHost --> UseUsernamePassword : UseUsernamePassword(string username, string password)SetHost --> UseWindowsAuthentication : UseWindowsAuthentication()UseUsernamePassword --> [*] : Build() return stringUseWindowsAuthentication --> [*] : Build() return string如圖:
ConnectionStringBuilder有了設計,接下來就是使用生成器啪嗒一下生成代碼,然后添加實現,這里只展示需要自己實現的內容:
#region?UserImplprivate?void?Core_SetHost(string?host) {_context.Host?=?host; }private?void?Core_UseUsernamePassword(string?username,?string?password) {_context.Username?=?username;_context.Password?=?password; }private?void?Core_UseWindowsAuthentication() {_context.IsWindowsAuthentication?=?true; }//?這里使用?ObjectVisitor?將一個模型的非空字段拼接在一起 private?static?readonly?ICachedObjectVisitor<ConnectionStringModel,?StringBuilder>?Builder?=default(ConnectionStringModel)!.V().WithExtendObject<ConnectionStringModel,?StringBuilder>().ForEach((name,?value,?sb)?=>?Append(name,?value,?sb)).Cache();private?static?void?Append(string?name,?object??value,?StringBuilder?sb) {if?(value?!=?null){sb.Append($"{name}={value};");} }private?string?Core_Build() {var?sb?=?new?StringBuilder();Builder.Run(_context,?sb);return?sb.ToString(); }#endregion下面是簡單的兩個測試用例:
public?class?ConnectionStringBuilderTest {[Test]public?void?UseUsernamePassword(){var?builder?=?new?ConnectionStringBuilder(new?ConnectionStringModel());var?re?=?builder.SetHost("localhost").UseUsernamePassword("yueluo",?"dalao").Build();re.Should().Be("Host=localhost;Username=yueluo;Password=dalao;");}[Test]public?void?UseWindowsAuthentication(){var?builder?=?new?ConnectionStringBuilder(new?ConnectionStringModel());var?re?=?builder.SetHost("localhost").UseWindowsAuthentication().Build();re.Should().Be("Host=localhost;IsWindowsAuthentication=True;");} }值得特別提出但是,這和直接使用 ConnectionStringModel 模型來構建字符串,通過 FluentAPI 的形式,約束了開發者能夠賦值的屬性。可以避免忘記對必要的屬性賦值或者錯誤賦值等等出錯情況。
Get 和 Delete 沒有 Body,Post 和 Put 才有
和上一節類型,我們使用 FluentAPI 來構建請求,但是需要滿足以下約束:
可以指定 Uri
Get 和 Delete 不能指定 Body,但是 Post 和 Put 可以
上設計:
stateDiagram[*] --> Get : Get()Get --> GetUri : SetUri(Uri uri) share _SetUriCore[*] --> Delete : Delete()Delete --> DeleteUri : SetUri(Uri uri) share _SetUriCore[*] --> Post : Post()Post --> PostUri : SetUri(Uri uri) share _SetUriCorePostUri --> SetContent : _SetContent share _SetContentCore[*] --> Put : Put()Put --> PutUri : SetUri(Uri uri) share _SetUriCorePutUri --> SetContent : _SetContent share _SetContentCoreSetContent --> [*] : _Build return HttpRequestMessageGetUri --> [*] : _Build return HttpRequestMessageDeleteUri --> [*] : _Build return HttpRequestMessage上圖:
RequestBuilder注意,這里引入了一些奇怪的關鍵詞 share ,由于這些關鍵詞還未全部定稿,因此不展開說明。
可以通過以下鏈接,查看生成的代碼和測試用例。
https://github.com/newbe36524/Newbe.ObjectVisitor/tree/main/src/Newbe.ObjectVisitor/Newbe.ObjectVisitor.Tests/HttpClientFluentApi
https://gitee.com/yks/Newbe.ObjectVisitor/tree/main/src/Newbe.ObjectVisitor/Newbe.ObjectVisitor.Tests/HttpClientFluentApi
造一輛汽車一定要四個輪子一個引擎
我們需要實現一個 CarBuilder,有一些約束:
CarBuilder 當且僅當在調用四次 AddWheel 和一次 AddEngine 之后才能出現 Build 方法
雖然限制了次數,但是,順序不能限定,什么順序都可以。
上設計:
stateDiagram[*] --> W1 : AddWheel(int size) share AddWheelW1 --> W2 : AddWheel(int size) share AddWheelW2 --> W3 : AddWheel(int size) share AddWheelW3 --> W4 : AddWheel(int size) share AddWheel[*] --> E : AddEngine(string engine) share AddEngineE --> WE1 : AddWheel(int size) share AddWheelWE1 --> WE2 : AddWheel(int size) share AddWheelWE2 --> WE3 : AddWheel(int size) share AddWheelWE3 --> WE4 : AddWheel(int size) share AddWheelW1 --> WE1 : AddEngine(string engine) share AddEngineW2 --> WE2 : AddEngine(string engine) share AddEngineW3 --> WE3 : AddEngine(string engine) share AddEngineW4 --> WE4 : AddEngine(string engine) share AddEngineWE4 --> [*] : Build() return Car上圖,這個圖從出發點出發,不論怎么走都會經過四次 AddWheel 和 一次 AddEngine:
CarBuilder注意,雖然設計看起來非常復雜,但是,需要手寫的代碼只有非常簡短的兩段:
#region?UserImplprivate?void?Shared_AddWheel(int?size) {if?(_context.Wheel1?==?0){_context.Wheel1?=?size;return;}if?(_context.Wheel2?==?0){_context.Wheel2?=?size;return;}if?(_context.Wheel3?==?0){_context.Wheel3?=?size;return;}if?(_context.Wheel4?==?0){_context.Wheel4?=?size;return;} }private?void?Shared_AddEngine(string?engine) {_context.Engine?=?engine; }private?Car?Core_Build() {return?_context; }#endregion可以通過以下鏈接,查看生成的代碼和測試用例。
https://github.com/newbe36524/Newbe.ObjectVisitor/tree/main/src/Newbe.ObjectVisitor/Newbe.ObjectVisitor.Tests/CarBuilder
https://gitee.com/yks/Newbe.ObjectVisitor/tree/main/src/Newbe.ObjectVisitor/Newbe.ObjectVisitor.Tests/CarBuilder
本篇總結
這是一個很有意思的設計,如果你對這個設計很感興趣,有新奇的想法,歡迎關注 Newbe.ObjectVisitor 項目,提出您的寶貴想法。
總結
以上是生活随笔為你收集整理的我画着图,FluentAPI 她自己就生成了的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: BenchmarkDotNet v0.1
 - 下一篇: .NET5都来了,你还不知道怎么部署到l