从这惊人的过程中:Applying Functional Principles in C#
我正在尝试应用领域驱动器设计,功能原理和面向铁路的编程方法。
有人可以帮我简化下面的代码行吗?
我知道我需要创建一些T扩展方法的结果,但是我无法使其正常工作。
伪代码的作用是...
(我们在访问数据库以进行更新操作之前执行此操作,如果我们收到无效的EmailAddress,则根本无法访问数据库)
(由于后来需要使用变量emailAddress,铁路被打破了((很可悲!!更糟糕的是...未检查失败的EmailAddress Result实体,应该检查)
(铁路再次断裂是由于后来需要使用变量playerResult :(真可悲!!。更糟糕的是...未检查未能返回的Result Player实体,应该)
(铁路再次断裂:(真可悲!!更糟糕的是……没有检查将EmailAddress添加到玩家集合的失败,应该这样)
为简洁起见,在下面减少了代码行
var emailAddress = Email.Create("jamesbond@gmail.com")
.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()
我执行了一些单元测试,并且代码可以正常工作,但就您所知,它完全面向铁路。
谢谢。
答案 0 :(得分:1)
我个人更喜欢使用局部变量。我认为它更易于维护。在处理诸如工作单元之类的非功能性设计时,尤其如此。但是,您可以使用功能性编程构造来完成您的工作。
您需要使用以下几项来删除本地变量。首先,您的单子需要一个bind
;这就是允许您解开多个值,然后将它们映射到新值的原因。第二个是元组。
我发现让我的域类型本身不了解功能类型很有用。因此,只有常规方法而已,不返回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
,因此可以在multi-from
子句中使用它来展开多个实例:
static async Task Main(string[] args)
{
var emailAddress = Try.Create(() => new Email("jamesbond@gmail.com"))
.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("jamesbond@gmail.com"))
.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("jamesbond@gmail.com"))
.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("jamesbond@gmail.com"))
.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");
}
要重申我在开始时所说的话,可以这样做,但是IMO丑陋且难以维护。最重要的是,C#是命令性语言,而不是功能性语言。在代码的部分中采用功能语言的 some 方面可以使其更美观;试图强制所有代码完全发挥功能是导致难以维护的秘诀。