Loading [MathJax]/jax/output/CommonHTML/config.js
社区首页 >问答首页 >公开异步函数的接口是一个泄漏的抽象吗?

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

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

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

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

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

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

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

你觉得怎么样?

EN

回答 8

Software Engineering用户

回答已采纳

发布于 2019-01-17 03: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 09:11:55

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

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

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

票数 11
EN

Software Engineering用户

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

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

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

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

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

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

复制
相关文章
Qt高并发
QThread是一个低级(low-level)类,适合用于显式地构建长期运行的线程。
全栈程序员站长
2022/09/03
1.5K0
【Qt】Qt的线程(两种QThread类的详细使用方式)「建议收藏」
Qt提供QThread类以进行多任务处理。与多任务处理一样,Qt提供的线程可以做到单个线程做不到的事情。例如,网络应用程序中,可以使用线程处理多种连接器。
全栈程序员站长
2022/09/01
10K0
Qt多线程编程之线程池
线程的创建及销毁需要与系统交互,会产生很大的开销。若需要频繁的创建线程建议使用线程池,有线程池维护一定数量的线程,当需要进行多线程运算时将运算函数传递给线程池即可。线程池会根据可用线程进行任务安排。
DeROy
2020/09/10
4.2K0
Qt多线程编程之线程池
QThread的用法
QThread类提供了一个与平台无关的管理线程的方法。一个QThread对象管理一个线程。QThread的执行从run()函数的执行开始,在Qt自带的QThread类中,run()函数通过调用exec()函数来启动事件循环机制,并且在线程内部处理Qt的事件。在Qt中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。QThread的使用方法有如下两种:
全栈程序员站长
2022/07/21
9130
QThread的用法
QT学习之如何使用Qthread(moveToThread方法)
通常在程序中需要同时做好几件事情,这时不可避免的会涉及到多线程的学习,QT学习过程中亦是如此,而QT中提供了 QThread,因为涉及到信号与槽,线程的使用也有些变化。在QThread的文档中有两种使用方法。
嵌入式小屋
2022/04/27
4.9K0
QT学习之如何使用Qthread(moveToThread方法)
qtcpsocket多线程_qtcpsocket接收数据
最近在写有关网络传输的项目,使用了Qt封装的QTcpSocket,但是发现很多的跨线程导致死机的问题,也许是我了解的不够深入吧。最开始是自己继承一个线程然后把主线程创建的套接字传递到子线程并且在子线程中写入数据遇到程序崩溃;否决后是考虑到了跨线程访问的问题,QTcpSocket跨线程崩溃说明它只能是在哪个线程创建就只能在哪个线程使用,这样想的话只需要把特别耗时的处理(数据准备操作)放到一个子线程中,然后把待写入数据通过信号槽的形式传递给QTcpSocket所在线程(其实就是主线程)中然后调用QTcpSocket::write()发送,但是这就让主线程在写数据了,如果数据不是很大倒也可以,看个人情况而定;最后决定使用QObject::moveToThread()的方式,因为我要不间断的发送大量的数据所以在一个子线程中操作才是明智的选择,就是把在主线程创建好的QTcpSocket对象通过QObject::moveToThread()放到一个子线程中操作(也就相当于是在子线程创建的),后续的QTcpSocket与主线程之间的操作都是通过信号槽形式进行的。
全栈程序员站长
2022/09/30
1.2K0
qtcpsocket多线程_qtcpsocket接收数据
QObject
说到Qt,你肯定首先想到的就是信号和槽,而这就不得不提到Qobject,这次和大家分享下QObject的相关内容,并对一些内容进行代码说明,方便理解。
用户5908113
2019/12/19
1.2K0
QObject
简化QThread示例代码
QThread官方文档在描述线程内容时,提供了一个线程操作的方法,该方法用在大部分场景下都可以适用,但使用起来略有繁琐,需要创建一个线程执行类Worker和控制类Controller,如下代码:
Qt君
2023/03/17
5640
简化QThread示例代码
Qt多线程编程
我在github仓库里面看到之前遗留的Qt_Demo,把文章相应的工程文件提交到仓库里面去,上次的简易聊天室也同步过去了,以及这篇文章的两个线程Demo。
DeROy
2020/09/01
2.2K0
Qt多线程编程
qt多线程编程实例_lgbt
每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作。
全栈程序员站长
2022/08/18
1.5K0
Qt线程使用的两种方法
一个QThread实例管理程序中的一个线程。QThread的执行开始于run()。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环。   你可以使用QObject::moveToThread()将工作对象移动到线程中使用。
Qt君
2019/07/15
2.8K0
QThread 的使用「建议收藏」
注意看倒数第二个单词,QThread 不等于线程,QThread 是负责管理线程的。 接下来看文档,我们清楚的知道QThread的两种使用方式。
全栈程序员站长
2022/09/01
1.5K0
Qt多线程1:QThread
Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确的创建一个线程,特别是如何正确的退出一个线程。
全栈程序员站长
2022/09/02
2.9K0
Qt 线程中使用socket(勘误)
之前写过两篇关于qt线程中使用socket的文章,昨天有小伙伴看了文章之后和我讨论,然后我发现有一篇文章有问题,今天特地更正一下,误导了之前的小伙伴表示歉意。
用户5908113
2019/09/09
1.5K0
Qt 线程中使用socket(勘误)
Qt线程中使用socket作为客户端通信(二)
Qt使用线程的方式有两种,一种是上次所说的继承QThread重新实现run()函数,在run()函数中一直循环处理;另一种则是继承QObject并使用moveToThread()函数将对象移到子线程中。由于继承QThread方式使用的并不规范,Qt官方强烈建议使用继承QObject的方式。
用户5908113
2019/07/29
3.1K0
QT多线程实战_Qt多线程开发项目
窗口本身就是一个死循环,在这样一个死循环中执行任何耗时的操作,都会导致程序崩溃。所以多线程对于窗口编程而言是必要的。
全栈程序员站长
2022/11/16
1.4K0
QT多线程实战_Qt多线程开发项目
你这样做是错的…(翻译文)
我们广泛使用IRC(网上交谈)与我们自己以及社区进行沟通。我在Freenode网络的Qt频道上闲逛,尽我所能帮助别人。
Qt君
2019/08/20
8610
qt tcpsocket_qt中udp通信
设想有如下场景:若干的客户端与服务器端建立连接,建立连接后,服务器端随机发送字符串给客户端,客户端打印输出。该节案例使用TCP编程。
全栈程序员站长
2022/09/30
9010
【Pyside6】Python多线程实现的选择与QThread的推荐实现方式
因为在网络上,特别是中文互联网上,关于Pyside6多线程的写法,特别是QThread的使用提及比较少,且较多使用不太推荐的写法,这篇博客主要是存下我自己参考的博客,希望对大家也有帮助。
Livinfly
2023/03/16
4.8K0
【Pyside6】Python多线程实现的选择与QThread的推荐实现方式
qtcpsocket断开_2020-05-06 QT子线程使用QTcpSocket连接服务器
分别是:1.继承QThread实现多线程2.继承QObject实现多线程(由于继承QObject的多线程实现方法更加灵活,Qt官方推荐使用该方法实现多线程)。这里将采用第二种方式实现多线程
全栈程序员站长
2022/10/03
1.5K0

相似问题

QObject::moveToThread并在线程内执行成员函数

21

QObject::moveToThread中的移动语义

36

如何在Qt5中检测QObject::moveToThread()失败?

22

带有自定义Qobject的MovetoThread问题

26

如何在使用moveToThread时在PyQt5中正确退出moveToThread

155
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文