我正在读依赖注入原则、实践和模式一书,我读到了关于漏抽象的概念,这本书很好地描述了这个概念。
现在,我正在使用依赖项注入重构C#代码库,以便使用异步调用而不是阻塞调用。为此,我正在考虑一些接口,这些接口表示代码库中的抽象,需要重新设计,以便可以使用异步调用。
例如,考虑以下接口,表示应用程序用户的存储库:
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
}
根据书中的定义,泄漏的抽象是一种设计时考虑到特定实现的抽象,因此某些实现细节通过抽象本身“泄漏”。
我的问题是:我们是否可以考虑使用异步设计的接口(如IUserRepository )作为漏取抽象的一个示例?
当然,并非所有可能的实现都与异步有关:只有进程外实现(例如SQL实现)才需要异步,但是内存存储库中不需要异步(实际上,如果接口公开异步方法,则实现接口的内存版本可能更困难,例如,您可能必须在方法实现中返回类似于Task.CompletedTask或Task.FromResult(用户)的内容)。
你觉得怎么样?
发布于 2019-01-17 11:55:20
当然,我们可以调用漏抽象定律,但这并不特别有趣,因为它假定所有抽象都是泄漏的。人们可以支持和反对这个猜想,但如果我们不理解抽象的含义和漏的含义,那就没有帮助了。因此,我将首先尝试描述我如何看待这些术语:
我最喜欢的抽象定义来自罗伯特·C·马丁(RobertC.Martin)的APPP:
“抽象是对本质的放大,对不相关的东西的消除。”
因此,接口本身并不是抽象的。只有当它们把重要的东西表露出来,并隐藏其余的东西时,它们才是抽象的。
书中的依赖注入原则、模式和实践定义了依赖注入(DI)上下文中的术语泄漏抽象。多态性和坚实的原则在这方面起着很大的作用。
从依赖反演原理 (DIP)中再次引用APPP如下:
“客户端...拥有抽象接口”
这意味着客户端(调用代码)定义了他们所需的抽象,然后你去实现这个抽象。
在我看来,泄漏的抽象是一种违反DIP的抽象,它以某种方式包含了客户不需要的一些功能。
实现业务逻辑的客户端通常使用DI将自己与某些实现细节(例如,通常是数据库)分离开来。
考虑一个处理餐馆预订请求的域对象:
public class MaîtreD : IMaîtreD
{
public MaîtreD(int capacity, IReservationsRepository repository)
{
Capacity = capacity;
Repository = repository;
}
public int Capacity { get; }
public IReservationsRepository Repository { get; }
public int? TryAccept(Reservation reservation)
{
var reservations = Repository.ReadReservations(reservation.Date);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (Capacity < reservedSeats + reservation.Quantity)
return null;
reservation.IsAccepted = true;
return Repository.Create(reservation);
}
}
在这里,IReservationsRepository
依赖项完全由客户端MaîtreD
类确定:
public interface IReservationsRepository
{
Reservation[] ReadReservations(DateTimeOffset date);
int Create(Reservation reservation);
}
这个接口是完全同步的,因为MaîtreD
类不需要它是异步的。
您可以轻松地将接口更改为异步的:
public interface IReservationsRepository
{
Task<Reservation[]> ReadReservations(DateTimeOffset date);
Task<int> Create(Reservation reservation);
}
然而,MaîtreD
类不需要这些方法是异步的,因此现在违反了DIP。我认为这是一个漏洞百出的抽象,因为实现细节迫使客户端进行更改。TryAccept
方法现在也必须变成异步的:
public async Task<int?> TryAccept(Reservation reservation)
{
var reservations =
await Repository.ReadReservations(reservation.Date);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (Capacity < reservedSeats + reservation.Quantity)
return null;
reservation.IsAccepted = true;
return await Repository.Create(reservation);
}
域逻辑是异步的没有内在的理由,但是为了支持实现的异步性,现在需要这样做。
在悉尼,2018年,我做了一个关于这个话题的演讲。在其中,我还概述了一种不会泄漏的替代方案。我也将在2019年的几次会议上发表这个演讲,但现在我重新命名为异步注入。
我还计划发表一系列的博客文章,以配合谈话。这些文章已经写好了,放在我的文章队列中,等待发布,所以请继续关注。
发布于 2019-01-15 17:11:55
这根本不是一个漏洞百出的抽象。
异步是函数定义的根本改变--它意味着调用返回时任务还没有完成,但也意味着您的程序流几乎会立即继续,而不是长时间延迟。执行相同任务的异步和同步函数本质上是不同的函数。异步不是实现细节。这是函数定义的一部分。
如果该函数公开了该函数是如何异步的,这将是泄漏的。你(不应该)关心它是如何实现的。
发布于 2019-01-14 15:02:20
方法的async
属性是一个标记,它指示需要特殊的注意和处理。因此,它需要泄漏到世界上。异步操作很难正确组合,因此提醒API用户是非常重要的。
相反,如果您的库正确地管理了它本身内的所有异步活动,那么您就可以不让async
“从API中泄漏”。
软件有四个方面的困难:数据、控制、空间和时间。异步操作涵盖所有四个维度,因此,需要最谨慎的操作。
https://softwareengineering.stackexchange.com/questions/385482
复制相似问题