来自这个令人惊叹的类(class):Applying Functional Principles in C#
我正在尝试应用域驱动设计、功能原理和面向铁路的编程方法。
有人可以帮我简化下面的代码行吗?
我知道我需要创建一些 T 扩展方法的结果,我尝试过,但无法让它们工作。
伪代码的作用是......
- 创建电子邮件值对象 -> Email.Create
- 创建 EmailAddress 实体 -> EmailAddress.Create
- 结果存储在 T 变量 emailAddress 的 Result 中
(我们在访问数据库进行更新操作之前执行此操作,如果我们得到无效的电子邮件地址,则根本没有意义访问数据库)
(由于稍后需要使用变量 emailAddress,铁路被破坏了:(太伤心了!!。更糟糕的是......失败的 EmailAddress 结果实体没有被检查,它应该被检查)
- 通过播放器存储库按 ID 获取播放器 ->repository.GetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")
- 结果存储在T变量playerResult的Result中
(由于稍后需要提供变量playerResult,铁路再次被破坏:(太伤心了!!。更糟糕的是......未能返回结果的Player实体未被检查,它应该是)
- 将新创建的 EmailAddress 添加到玩家电子邮件地址集合中 -> p.AddEmailAddress(emailAddress.Value)
(铁路再次损坏:(太悲伤了!!。更糟糕的是...未检查将电子邮件地址添加到玩家集合中的失败,而且应该检查)
- 最后异步保存更改并返回受影响的数量或行数。
下面为简洁起见减少了代码行
var emailAddress = Email.Create("<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="caa0aba7afb9a8a5a4ae8aada7aba3a6e4a9a5a7" rel="noreferrer noopener nofollow">[email protected]</a>")
.OnSuccess(email => EmailAddress.Create(email, default));
var playerResult = await emailAddress.OnSuccess(e => repository.GetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")).ToResult(""));
var wasAdded = playerResult.OnSuccess(p => p.AddEmailAddress(emailAddress.Value));
var wasSaved = await wasAdded.OnSuccess(a => unitOfWork.SaveChangesAsync());
var message = wasSaved
.Ensure(r => r > 0, "No record were saved")
.OnBoth(result => result.IsSuccess ? "Ok" : result.Error);
下面是方法的签名
Email.Create -> public static Result<Email> Create(string email)
EmailAddress.Create -> public static Result<EmailAddress> Create(Email mailAddress, string mailAddressInstructions)
GetAsync -> public Task<Maybe<Player>> GetAsync(string id)
AddEmailAddress -> public Result<bool> AddEmailAddress(EmailAddress emailAddress)
SaveChangesAsync -> public async Task<int> SaveChangesAsync()
我执行了一些单元测试,代码正在运行,但据您所见,距离面向铁路还很远。
提前致谢。
最佳答案
就我个人而言,我更喜欢使用局部变量;我认为它更易于维护。在处理非功能性设计(例如工作单元)时尤其如此。但是,您想要做的事情可以使用函数式编程结构来实现。
您需要使用一些东西来删除局部变量。首先,你需要一个bind
为你的单子(monad);这就是允许您解包多个值然后将它们映射到新值的原因。第二个是元组。
我发现让我的域类型本身不了解函数类型是很有用的。所以只有常规方法之类的,不返回 Result<T>
类型:
private static Task<Player> RepositoryGetAsync(string id) => Task.FromResult(new Player());
private static Task<int> RepositorySaveChangesAsync() => Task.FromResult(0);
public sealed class Player
{
public bool AddEmailAddress(EmailAddress address) => true;
}
public sealed class Email
{
public Email(string address) => Address = address ?? throw new ArgumentNullException(nameof(address));
public string Address { get; }
}
public sealed class EmailAddress
{
public static EmailAddress Create(Email address, int value) => new EmailAddress();
}
这是代码的第一个版本 - 使用 my own Try<T>
type ,因为我不确定哪个Maybe<T>
和Result<T>
您正在使用的类型。我的Try<T>
类型支持SelectMany
这样它就可以在多个中使用-from
用于解开多个实例的子句:
static async Task Main(string[] args)
{
var emailAddress = Try.Create(() => new Email("<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84eee5e9e1f7e6ebeae0c4e3e9e5ede8aae7ebe9" rel="noreferrer noopener nofollow">[email protected]</a>"))
.Map(email => EmailAddress.Create(email, default));
var playerResult = await Try.Create(() => RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6"));
var wasAdded = from address in emailAddress
from player in playerResult
select player.AddEmailAddress(address);
var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
if (wasSaved.Value == 0)
throw new Exception("No records were saved");
}
如果我们开始使用元组,我们就可以组合几个变量。语法有点尴尬(例如,解构 lambda 参数),但它是可行的:
static async Task Main(string[] args)
{
var emailAddressAndPlayerResult = await Try.Create(() => new Email("<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bfd5ded2daccddd0d1dbffd8d2ded6d391dcd0d2" rel="noreferrer noopener nofollow">[email protected]</a>"))
.Map(email => EmailAddress.Create(email, default))
.Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")));
var wasAdded = emailAddressAndPlayerResult.Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address));
var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
if (wasSaved.Value == 0)
throw new Exception("No records were saved");
}
一旦我们混合了元组,其余变量就可以很好地折叠起来。剩下唯一尴尬的部分是await
通常需要括号。例如,此代码与上面相同:
static async Task Main(string[] args)
{
var wasAdded =
(
await Try.Create(() => new Email("<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d5bfb4b8b0a6b7babbb195b2b8b4bcb9fbb6bab8" rel="noreferrer noopener nofollow">[email protected]</a>"))
.Map(email => EmailAddress.Create(email, default))
.Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")))
)
.Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address));
var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
if (wasSaved.Value == 0)
throw new Exception("No records were saved");
}
现在,当我们组合这些变量时,您可以看到 await
是如何变化的。表达式尤其会在道路上造成“颠簸”。丑陋,但可行。要删除最后一个变量:
static async Task Main(string[] args)
{
var wasSaved =
await
(
await Try.Create(() => new Email("<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f99398949c8a9b96979db99e94989095d79a9694" rel="noreferrer noopener nofollow">[email protected]</a>"))
.Map(email => EmailAddress.Create(email, default))
.Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")))
)
.Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address))
.Map(_ => RepositorySaveChangesAsync());
if (wasSaved.Value == 0)
throw new Exception("No records were saved");
}
重申一下我在开始时所说的,这样做是可能的,但在我看来,它很丑陋且不易维护。最重要的是,C# 是一种命令式语言,而不是一种函数式语言。在代码的部分中采用函数式语言的某些方面可以使其更加优雅;试图强制所有代码都功能齐全会导致不可维护性。
关于c# - CSharpFunctionalExtensions 和面向铁路的编程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56246749/