我画着图,FluentAPI 她自己就生成了
qiyuwang 2024-10-10 11:28 8 浏览 0 评论
在 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 状态图标记。转换为图形即为下面这个效果。不需要过多的解释就可以理解:
有了这个状态图之后,使用 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 UserImpl
private 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
如图:
创建数据库链接字符串
前面的示例或许缺乏生产实际,现在添加一个生产示例。我们现在要实现一个 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 string
UseWindowsAuthentication --> [*] : Build() return string
如图:
有了设计,接下来就是使用生成器啪嗒一下生成代码,然后添加实现,这里只展示需要自己实现的内容:
#region UserImpl
private 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(#34;{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 _SetUriCore
PostUri --> SetContent : _SetContent share _SetContentCore
[*] --> Put : Put()
Put --> PutUri : SetUri(Uri uri) share _SetUriCore
PutUri --> SetContent : _SetContent share _SetContentCore
SetContent --> [*] : _Build return HttpRequestMessage
GetUri --> [*] : _Build return HttpRequestMessage
DeleteUri --> [*] : _Build return HttpRequestMessage
上图:
注意,这里引入了一些奇怪的关键词 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 AddWheel
W1 --> W2 : AddWheel(int size) share AddWheel
W2 --> W3 : AddWheel(int size) share AddWheel
W3 --> W4 : AddWheel(int size) share AddWheel
[*] --> E : AddEngine(string engine) share AddEngine
E --> WE1 : AddWheel(int size) share AddWheel
WE1 --> WE2 : AddWheel(int size) share AddWheel
WE2 --> WE3 : AddWheel(int size) share AddWheel
WE3 --> WE4 : AddWheel(int size) share AddWheel
W1 --> WE1 : AddEngine(string engine) share AddEngine
W2 --> WE2 : AddEngine(string engine) share AddEngine
W3 --> WE3 : AddEngine(string engine) share AddEngine
W4 --> WE4 : AddEngine(string engine) share AddEngine
WE4 --> [*] : Build() return Car
上图,这个图从出发点出发,不论怎么走都会经过四次 AddWheel 和 一次 AddEngine:
注意,虽然设计看起来非常复杂,但是,需要手写的代码只有非常简短的两段:
#region UserImpl
private 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 项目,提出您的宝贵想法。
发布说明
- Newbe.ObjectVisitor 0.3.7 发布,自动生成 FluentAPI
- Newbe.ObjectVisitor 0.2.10 发布,更花里胡哨
- Newbe.ObjectVisitor 0.1.4 发布,初始版本
使用样例
- Newbe.ObjectVisitor 样例 1
番外分享
- 寻找性能更优秀的动态 Getter 和 Setter 方案
- 寻找性能更优秀的不可变小字典
- 我画着图,FluentAPI 她自己就生成了
GitHub 项目地址:https://github.com/newbe36524/Newbe.ObjectVisitor
Gitee 项目地址:https://gitee.com/yks/Newbe.ObjectVisitor
- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.ObjectVisitor/Generate-Fluent-API-by-Design/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关推荐
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
-
#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...
- 利用阿里云镜像在ubuntu上安装Docker
-
简介:...
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
-
UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...
- Ubuntu 16.10内部代号确认为Yakkety Yak
-
在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
-
在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
-
IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
-
本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...
- 怎么看电脑系统?(怎么看电脑系统配置)
-
要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...
- 如何查询 Linux 内核版本?这些命令一定要会!
-
Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...
- 深度剖析:Linux下查看系统版本与CPU架构
-
在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...
- 504 错误代码解析与应对策略(504错误咋解决)
-
在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...
- 猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录
-
10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...
- 域名解析的原理是什么?域名解析的流程是怎样的?
-
域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...
- Linux无法解析域名的解决办法(linux 不能解析域名)
-
如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。 此时,需要手动修改配置文件: /etc/resolv.conf 将域名解析服务器手动添加到配置文件中 该文件是DNS域名解...
- 域名劫持是什么?(域名劫持是什么)
-
域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
- 利用阿里云镜像在ubuntu上安装Docker
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
- Ubuntu 16.10内部代号确认为Yakkety Yak
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
- 怎么看电脑系统?(怎么看电脑系统配置)
- 如何查询 Linux 内核版本?这些命令一定要会!
- 深度剖析:Linux下查看系统版本与CPU架构
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)