清洁架构:结合交互器

Posted

技术标签:

【中文标题】清洁架构:结合交互器【英文标题】:Clean Architecture: Combining Interactors 【发布时间】:2017-10-03 20:38:24 【问题描述】:

我最近偶然发现了 Bob 叔叔的 Clean Architecture,我很想知道 Interactor 是否可以执行其他 Interactor。

例如,这些是我现在的交互器:getEmptyAlbums、getOtherAlbums。两者都有回调,分别返回专辑列表(专辑模型的 ArrayList)。

我是否允许有一个名为 getAllAlbums 的交互器在它的运行块中执行前两个交互器?

@Override
public void run() 
    getEmptyAlbums.execute();       


void onEmptyAlbumsReceived(ArrayList<Album albums)
     getOtherAlbums.execute;

void onOtherAlbumsReceived(ArrayList<Album albums)
         mMainThread.post(new Runnable() 
         callback.onAlbumsReceived(albums);
     
);

【问题讨论】:

【参考方案1】:

我一直在思考同样的事情,在发现关于该主题的内容很少之后,我得出的结论是“是的”,这可能是最好的选择。

我的推理如下:

    单一职责:如果您不能聚合用例,那么每个用例真的都不能是单一职责。如果没有聚合,这意味着域逻辑最终会出现在表示层中,从而无法达到目的。 DRY:用例可以共享,应该在有意义的地方。只要用例的意图是相同的。显然,这应该在完成之前考虑清楚。根据我的经验,除了下一点之外,很少需要这样做。 Orchestrator 类:例如,如果您需要获取多个数据源并持久保存到存储库。需要一个将运行所有这些子用例的用例,以确保正确实现操作顺序和并发性等内容。我认为这是调用其他用例的最有说服力的原因。

为了保持单一职责,我会考虑将聚合用例限制为,即执行这些用例并进行任何最终转换。

鉴于这个问题的年龄,我很想知道您对此采取了何种方式以及遇到的问题。

【讨论】:

(我刚刚搜索了这个问题)并且由于 DRY 原则而倾向于“是”。我可以看到一个Interactor 对象(用例)创建一个新的RequestModel 并将其传递给一个单独的Interactor 对象。但是,正如您所说,“关于这个主题的内容很少”。 我使用了执行其他交互器的交互器,以避免我的表示层过于混乱和庞大,并且没有遇到任何问题。 我也是这么看的。有关此主题的更详细示例,请参阅plainionist.github.io/Implementing-Clean-Architecture-UseCases 我同意,这就是这样做的地方。这种方式比在Presentation层结合这个逻辑要好得多。 根据我的经验 - 永远不要那样做或得到 spagetti-code :-) 交互器应该独立更改,因为它们是应用程序的不同部分。如果您给了交互器使用另一个交互器的机会 - 出了点问题,您应该将逻辑从交互器转移到实体或网关。如果我们谈论这个具体的问题 - 这是完全不正确的。为什么?因为如果您对专辑实体有形容词 - 它是它自己的属性【参考方案2】:

看看令人惊叹的“清洁建筑”一书的第 16 章。鲍勃叔叔在名为“复制”的部分回答了这个问题。有两种类型的重复:

真正的重复 — 引入更改会影响存在重复代码的多个位置。

意外重复——现在的代码很相似,但背后的想法不同,随着时间的推移,代码也会变得不同。

在真正重复的情况下,您可以将用例耦合起来,但要小心,因为随着软件的发展,在意外重复的情况下拆分它们会更加困难。

【讨论】:

这是一个非常好的观点,除非两个操作的意图相同,否则 DRY 不适用。【参考方案3】:

我的答案是否定的。让我解释一下原因:

    这将打破界限

干净架构最重要的概念之一是边界。每个用例都定义了一个边界,即系统的垂直层。因此,没有理由让一个用例知道另一个用例的存在。该垂直层允许获得用例的独立开发能力和可部署性。想象一下,我们作为一个团队工作,您开发 GetEmptyAlbums 用例,而我处理 GetAllAlbums 用例。如果我自己调用您的用例,我们不是独立开发的。我们都没有实现独立的可部署性。垂直边界中断。有关详细信息,请参阅 Clean Architecture 书的第 152 页和第 16 章。

    SRP 也会被破坏

假设 GetEmptyAlbums 业务规则因任何原因发生更改。您将需要重构该用例。现在也许你需要接受一些输入。如果 GetAllAlbums 调用 GetEmptyAlbums,这个用例也必须重构。换句话说,通过耦合用例,您正在添加更多职责。因此,SRP 中断。

    DRY 仍在投诉

有两种重复:真实重复和意外重复。通过定义 2 个或更多彼此非常相似的用例,您会意外重复。这是偶然的,因为将来可能会因为不同的原因而变得不同(这很重要)。有关此概念,请参见第 154 页。

    测试变得更加脆弱

与 SRP 非常相关。如果您在用例 A 上更改某些内容,并且 C 调用 A,则不仅 A 测试会中断,C 测试也会中断。

总之,答案是否定的,您不能从另一个用例交互器调用用例交互器。但是,如果您想要实现纯粹的清洁架构方法,则此规则适用,这并不总是正确的决定。

另外需要指出的是,用例必须声明输入和输出数据结构。我不确定您的 Album 类是否是实体,但如果是,则存在问题。正如鲍勃叔叔所说:“我们不想作弊并在边界之间传递实体对象”(第 207 页)。

【讨论】:

我们可以在不同的用例中重用存储库吗?还是功能中的所有内容都应该独立于另一个功能? 每个用例都应该有自己的存储库。你会产生意外的重复。但是您将获得一个包含完全隔离的域、数据和表示的垂直层。但是,请记住,这不是最终的软件架构。这对大团队确实很有帮助,但对于小团队来说,完美地应用它可能有点过头了。您必须问自己,在另一个用例中重用此存储库时,是否需要出于多个原因进行更改?并据此做出决定。无论如何,你可以(并且应该)总是重构 如果您不在测试中模拟您的依赖项,您将处于一个受伤的世界。这包括其他用例。如果您所做的只是获取项目,那么您应该从存储库中获取它们。如果有业务逻辑需要作为其中的一部分进行处理,那么它可能很常见并且 DRY 方法是有意义的。 @BenNeill 我同意您关于直接调用存储库以避免出现中间人用例的观点。现在,当我们谈论鲍勃叔叔的清洁架构时,存储库应该被封装为只能从它们的交互器中调用。一个原因是存储库返回实体,并且演示者不应该使用那些(因为视图不会使用所有数据或调用实体方法)。此外,应该封装存储库以避免从交互器外部使用它们。就像我说的,这就是 Clean Architecture 所建立的。这并不意味着是每个人或每一刻的最佳选择:) 对了,关于 DRY,The Pragmatic Progammer 20 周年版对这个原则做了一些说明。它表明“重复”代码并不一定意味着违反 DRY。第 31 页的提示 25(不要重复自己)。【参考方案4】:

我对鲍勃叔叔的工作非常陌生,而且我也在经历这些完全相同的问题。

我对维护 SRP 和不重复自己 (DRY) 用例的回答是将用例与交互器分开。这可能是矫枉过正,但对我来说确实效果很好。

我将我的用例放在他们自己的文件中,与交互者分开,以便所有独立的交互者可以使用他们想要和共享的任何用例。一直以来,交互者只是“使用”(导入、依赖等)它想要的任何用例。

这样做使我的交互器变得非常简单,实际上只是一个容器,用于所需的依赖注入和一些类级别的成员变量。

因此,总而言之,getAllAlbums、getEmptyAlbums 和 getOtherAlbums 用例成为它们自己的文件并遵循 SRP,并且您有一个交互器类,可以随意聚合和/或按顺序将用例缝合在一起。

最近我也让我的用例只做实际的业务逻辑,而不包括来自依赖注入网关的东西,比如数据库或网络调用。然后我将这些依赖网关操作的代码放在操作用例的方法中......

现在,如果您在用例中只有“黑盒”业务逻辑概念,您可以在不包括紧密耦合的依赖项的情况下进行测试。因此,例如,如果您正在制作“井字游戏”游戏,您的用例(快速浏览)将仅使用“井字游戏”的语言,而不是“保存”、“提交”或“获取[X]”。您可以在交互器测试或网关本身中保存测试这些内容。

【讨论】:

我在我的实现中也得出了这个结论,很好的评论@goredefex

以上是关于清洁架构:结合交互器的主要内容,如果未能解决你的问题,请参考以下文章

清洁架构,数据请求协调器,演示者或用例/交互器?

清洁架构 - 如何处理数据库事务?

清洁架构:用例输出端口

Unity VR开发结合VRTK4.0:将浮点数从交互器传递到可交互对象

DDD层和清洁架构

模块划分常用架构