我画着图,FluentAPI 她自己就生成了
在 Newbe.ObjectVistor 0.3 版本中我們非常興奮的引入了一個(gè)緊張刺激的新特性:使用狀態(tài)圖來(lái)生成任意給定的 FluentAPI 設(shè)計(jì)。
開(kāi)篇摘要
在非常多優(yōu)秀的框架中都存在一部分 FluentAPI 的設(shè)計(jì)。這種 API 設(shè)計(jì)更加符合人類(lèi)自言語(yǔ)言描述。使得代碼更加具備可讀性。
在 Newbe.ObjectVistor 0.3 版本中,我們?cè)O(shè)計(jì)引入了一種使用狀態(tài)圖來(lái)自動(dòng)生成 FluentAPI 代碼的機(jī)制。極大了簡(jiǎn)化了 FluentAPI 實(shí)現(xiàn)所需要的腦力勞動(dòng)。
本篇我們將通過(guò)一些示例,來(lái)了解一下當(dāng)前版本中該特性的主要效果。
整數(shù)累加 FluentAPI
假如,我們現(xiàn)在需要實(shí)現(xiàn)下面這樣效果的一個(gè) 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); }這個(gè) API 使用 FluentAPI 的方式來(lái)表述一個(gè)累加的過(guò)程。
為了實(shí)現(xiàn)這個(gè) API 設(shè)計(jì),在 Newbe.ObjectVisitor 0.3 中,使用下面這樣一個(gè)狀態(tài)圖標(biāo)記表述這個(gè) API 設(shè)計(jì):
stateDiagram[*] --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : AddNumber(int number)AddNumber --> [*] : Sum() return int這實(shí)際上是 mermaid 狀態(tài)圖標(biāo)記。轉(zhuǎn)換為圖形即為下面這個(gè)效果。不需要過(guò)多的解釋就可以理解:
SumBuilder有了這個(gè)狀態(tài)圖之后,使用 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 ///?此處省略了自動(dòng)生成的固定代碼部分,請(qǐng)到倉(cāng)庫(kù)中查看#endregion} }有了這個(gè)模板之后,只要實(shí)現(xiàn) Core_AddNumber 和 Core_Sum,一個(gè)符合預(yù)期設(shè)計(jì)的 FluentAPI 就完成了!
累加后累乘
現(xiàn)在,我們稍微改變一下需求。上節(jié)我們實(shí)現(xiàn)的是一個(gè) 1+2+3 這樣的累加效果。現(xiàn)在我們需要一個(gè) (1+2+3)*(4+5+6)*(7+8+9+10) 這樣的效果。
示例的調(diào)用代碼如下:
[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); }為了實(shí)現(xiàn)這個(gè)效果,我們修改一下?tīng)顟B(tài)圖,增加一條新的規(guī)則,得到:
stateDiagram[*] --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : AddNumber(int number)AddNumber --> AddNumber : NextFactor()AddNumber --> [*] : Sum() return int如圖:
MultipleSumBuilder創(chuàng)建數(shù)據(jù)庫(kù)鏈接字符串
前面的示例或許缺乏生產(chǎn)實(shí)際,現(xiàn)在添加一個(gè)生產(chǎn)示例。我們現(xiàn)在要實(shí)現(xiàn)一個(gè) ConnectionStringBuilder 用來(lái)創(chuàng)建數(shù)據(jù)庫(kù)連接字符串,其中有以下限制:
必須指定 Host。
身份認(rèn)證方式必須且只能指定一種,要么是用戶(hù)名密碼方式,要么是 Windows 憑據(jù)。
首先,我們有一個(gè)模型來(lái)保存上面提到的數(shù)據(jù)。
public?class?ConnectionStringModel {public?string?Host?{?get;?set;?}public?string?Username?{?get;?set;?}public?string?Password?{?get;?set;?}public?bool??IsWindowsAuthentication?{?get;?set;?} }接著,我們直接使用狀態(tài)圖來(lái)設(shè)計(jì)這個(gè) FluentAPI。設(shè)計(jì)結(jié)果如下:
stateDiagram[*] --> SetHost : SetHost(string host)SetHost --> UseUsernamePassword : UseUsernamePassword(string username, string password)SetHost --> UseWindowsAuthentication : UseWindowsAuthentication()UseUsernamePassword --> [*] : Build() return stringUseWindowsAuthentication --> [*] : Build() return string如圖:
ConnectionStringBuilder有了設(shè)計(jì),接下來(lái)就是使用生成器啪嗒一下生成代碼,然后添加實(shí)現(xiàn),這里只展示需要自己實(shí)現(xiàn)的內(nèi)容:
#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?將一個(gè)模型的非空字段拼接在一起 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下面是簡(jiǎn)單的兩個(gè)測(cè)試用例:
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 模型來(lái)構(gòu)建字符串,通過(guò) FluentAPI 的形式,約束了開(kāi)發(fā)者能夠賦值的屬性。可以避免忘記對(duì)必要的屬性賦值或者錯(cuò)誤賦值等等出錯(cuò)情況。
Get 和 Delete 沒(méi)有 Body,Post 和 Put 才有
和上一節(jié)類(lèi)型,我們使用 FluentAPI 來(lái)構(gòu)建請(qǐng)求,但是需要滿(mǎn)足以下約束:
可以指定 Uri
Get 和 Delete 不能指定 Body,但是 Post 和 Put 可以
上設(shè)計(jì):
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注意,這里引入了一些奇怪的關(guān)鍵詞 share ,由于這些關(guān)鍵詞還未全部定稿,因此不展開(kāi)說(shuō)明。
可以通過(guò)以下鏈接,查看生成的代碼和測(cè)試用例。
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
造一輛汽車(chē)一定要四個(gè)輪子一個(gè)引擎
我們需要實(shí)現(xiàn)一個(gè) CarBuilder,有一些約束:
CarBuilder 當(dāng)且僅當(dāng)在調(diào)用四次 AddWheel 和一次 AddEngine 之后才能出現(xiàn) Build 方法
雖然限制了次數(shù),但是,順序不能限定,什么順序都可以。
上設(shè)計(jì):
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上圖,這個(gè)圖從出發(fā)點(diǎn)出發(fā),不論怎么走都會(huì)經(jīng)過(guò)四次 AddWheel 和 一次 AddEngine:
CarBuilder注意,雖然設(shè)計(jì)看起來(lái)非常復(fù)雜,但是,需要手寫(xiě)的代碼只有非常簡(jiǎn)短的兩段:
#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可以通過(guò)以下鏈接,查看生成的代碼和測(cè)試用例。
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
本篇總結(jié)
這是一個(gè)很有意思的設(shè)計(jì),如果你對(duì)這個(gè)設(shè)計(jì)很感興趣,有新奇的想法,歡迎關(guān)注 Newbe.ObjectVisitor 項(xiàng)目,提出您的寶貴想法。
總結(jié)
以上是生活随笔為你收集整理的我画着图,FluentAPI 她自己就生成了的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BenchmarkDotNet v0.1
- 下一篇: 天际数见数据质量巡检架构优化