发布
社区首页 >问答首页 >公开异步函数的接口是一个泄漏的抽象吗?

公开异步函数的接口是一个泄漏的抽象吗?
EN

Software Engineering用户
提问于 2019-01-14 14:33:31
回答 8查看 3.6K关注 0票数 14

我正在读依赖注入原则、实践和模式一书,我读到了关于漏抽象的概念,这本书很好地描述了这个概念。

现在,我正在使用依赖项注入重构C#代码库,以便使用异步调用而不是阻塞调用。为此,我正在考虑一些接口,这些接口表示代码库中的抽象,需要重新设计,以便可以使用异步调用。

例如,考虑以下接口,表示应用程序用户的存储库:

代码语言:javascript
代码运行次数:0
复制
public interface IUserRepository 
{
  Task<IEnumerable<User>> GetAllAsync();
}

根据书中的定义,泄漏的抽象是一种设计时考虑到特定实现的抽象,因此某些实现细节通过抽象本身“泄漏”。

我的问题是:我们是否可以考虑使用异步设计的接口(如IUserRepository )作为漏取抽象的一个示例?

当然,并非所有可能的实现都与异步有关:只有进程外实现(例如SQL实现)才需要异步,但是内存存储库中不需要异步(实际上,如果接口公开异步方法,则实现接口的内存版本可能更困难,例如,您可能必须在方法实现中返回类似于Task.CompletedTask或Task.FromResult(用户)的内容)。

你觉得怎么样?

EN

回答 8

Software Engineering用户

回答已采纳

发布于 2019-01-17 11:55:20

当然,我们可以调用漏抽象定律,但这并不特别有趣,因为它假定所有抽象都是泄漏的。人们可以支持和反对这个猜想,但如果我们不理解抽象的含义和漏的含义,那就没有帮助了。因此,我将首先尝试描述我如何看待这些术语:

抽象

我最喜欢的抽象定义来自罗伯特·C·马丁(RobertC.Martin)的APPP

“抽象是对本质的放大,对不相关的东西的消除。”

因此,接口本身并不是抽象的。只有当它们把重要的东西表露出来,并隐藏其余的东西时,它们才是抽象的。

Leaky

书中的依赖注入原则、模式和实践定义了依赖注入(DI)上下文中的术语泄漏抽象。多态性和坚实的原则在这方面起着很大的作用。

依赖反演原理 (DIP)中再次引用APPP如下:

“客户端...拥有抽象接口”

这意味着客户端(调用代码)定义了他们所需的抽象,然后你去实现这个抽象。

在我看来,泄漏的抽象是一种违反DIP的抽象,它以某种方式包含了客户不需要的一些功能。

同步依赖关系

实现业务逻辑的客户端通常使用DI将自己与某些实现细节(例如,通常是数据库)分离开来。

考虑一个处理餐馆预订请求的域对象:

代码语言:javascript
代码运行次数:0
复制
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类确定:

代码语言:javascript
代码运行次数:0
复制
public interface IReservationsRepository
{
    Reservation[] ReadReservations(DateTimeOffset date);
    int Create(Reservation reservation);
}

这个接口是完全同步的,因为MaîtreD类不需要它是异步的。

异步依赖项

您可以轻松地将接口更改为异步的:

代码语言:javascript
代码运行次数:0
复制
public interface IReservationsRepository
{
    Task<Reservation[]> ReadReservations(DateTimeOffset date);
    Task<int> Create(Reservation reservation);
}

然而,MaîtreD类不需要这些方法是异步的,因此现在违反了DIP。我认为这是一个漏洞百出的抽象,因为实现细节迫使客户端进行更改。TryAccept方法现在也必须变成异步的:

代码语言:javascript
代码运行次数:0
复制
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年的几次会议上发表这个演讲,但现在我重新命名为异步注入。

我还计划发表一系列的博客文章,以配合谈话。这些文章已经写好了,放在我的文章队列中,等待发布,所以请继续关注。

票数 8
EN

Software Engineering用户

发布于 2019-01-15 17:11:55

这根本不是一个漏洞百出的抽象。

异步是函数定义的根本改变--它意味着调用返回时任务还没有完成,但也意味着您的程序流几乎会立即继续,而不是长时间延迟。执行相同任务的异步和同步函数本质上是不同的函数。异步不是实现细节。这是函数定义的一部分。

如果该函数公开了该函数是如何异步的,这将是泄漏的。你(不应该)关心它是如何实现的。

票数 11
EN

Software Engineering用户

发布于 2019-01-14 15:02:20

方法的async属性是一个标记,它指示需要特殊的注意和处理。因此,它需要泄漏到世界上。异步操作很难正确组合,因此提醒API用户是非常重要的。

相反,如果您的库正确地管理了它本身内的所有异步活动,那么您就可以不让async“从API中泄漏”。

软件有四个方面的困难:数据、控制、空间和时间。异步操作涵盖所有四个维度,因此,需要最谨慎的操作。

票数 6
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/385482

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档