【问题标题】:Clean Architecture: How to reflect the data layer's changes in the UIClean Architecture:如何在 UI 中反映数据层的变化
【发布时间】:2015-12-24 02:02:19
【问题描述】:

我正在尝试基于 Android 中的Uncle Bob's Clean Architecture 进行设计。

问题:

我想解决的是如何使在一个存储库中生成的更改反映在应用程序的其他部分,例如其他存储库或视图。

示例

我为此示例设计了一个非常简化的示例。请注意,边界接口已被移除以保持图表较小。

想象一个显示视频列表(带有标题、缩略图和点赞次数)的应用,点击视频可以查看详细信息(您可以在此处点赞/不点赞视频)。

此外,该应用还有一个统计系统,可以统计用户喜欢或不喜欢的视频数量。

这个应用程序的主要类可能是:

对于视频部分/模块:

对于统计部分/模块:

目标

现在假设您检查了自己的统计数据,然后浏览视频列表,打开其中的详细信息,然后单击“赞”按钮。

like 发送到服务器后,应用程序的几个元素应注意更改:

  • 当然,详细视图应该随着更改而更新(这可以通过回调来完成,所以没问题)
  • 视频列表应更新给定视频的“喜欢”计数
  • StatsRepository 可能希望在对新视频进行投票后更新/使缓存无效
  • 如果统计信息列表可见(想象一个分屏),它还应该显示更新的统计信息(或至少接收事件以重新查询数据)

问题

解决这种通信的常见模式是什么? 请尽可能完整地回答您的问题,指定事件的生成位置、它们如何通过应用程序传播等。

注意:完成答案将获得奖励

【问题讨论】:

    标签: java android architecture coding-style


    【解决方案1】:

    这是我个人的 5 美分,可能与您的“清洁架构”示例的关系不够密切。

    我通常会尝试在 android 活动和片段上强制使用一种 MVC,并使用发布/订阅进行通信。作为组件,我有处理业务逻辑和数据状态的模型类。它们的数据更改方法只能由通常是活动类并处理会话状态的控制器类调用。我使用片段来管理应用程序的不同视图部分以及这些片段下的视图(显然)。所有片段都订阅一个或多个主题。我使用我自己的简单 DataDistributionService 来处理不同的主题,从注册的发布者那里获取消息并将它们转发给所有订阅者。 (部分受 OMG 的 DDS 影响,但更原始)一个简单的应用程序只有一个主题,例如“主要”。

    视图交互(触摸等)的每个部分首先由其片段处理。该片段可能会在不发送通知的情况下更改一些内容。例如。如果应用程序的其余部分不需要知道/反应,则切换渲染数据元素的子范围。否则,片段会向 DDS 发布包含必要参数的 ViewRequest(...)。

    DDS 广播该消息并在某个时候到达控制器。这可以简单地是主要活动或特定控制器实例。应该只有一个控制器,以便请求只处理一次。控制器基本上有一长串请求处理代码。当请求到达时,控制器调用模型中的业务逻辑。控制器还处理其他与视图相关的事情,例如安排视图(选项卡)或为用户输入启动对话框(覆盖文件?)以及模型不应该知道但会影响的其他事情(抛出新的 NoOverWritePermissionException())

    模型更改完成后,控制器决定是否必须发送更新通知。 (通常是这样)。这样模型类就不需要监听或发送消息,只需要处理业务逻辑和一致的状态。更新通知由片段广播和接收,然后运行“updateFromModel()”。

    效果:
    命令是全局的。可以从可以访问 DDS 的任何地方发送任何 ViewRequest 或其他类型的请求。片段不必提供侦听器类,并且没有更高的实例必须为其实例化片段实现侦听器。如果一个新片段不需要新的请求,则可以在不更改控制器类的情况下添加它。

    模型类根本不需要了解通信。保持一致的状态并处理所有数据管理可能已经够难了。不需要消息处理或会话状态处理。但是,模型可能不会受到来自视图的恶意调用的保护。但这是一个普遍问题,如果模型必须在某些时候给出参考,则无法真正避免。如果您的应用程序适用于仅传递副本/平面数据的模型,则可能。但在某些时候,ArrayAdapter 只需要访问他应该在 gridview 中绘制的位图。如果你买不起副本,你总是有“视图改变模型调用”的风险。不同的战场...

    更新调用可能太简单了。如果片段的更新成本很高(OpenGL 片段重新加载纹理...),您希望获得更详细的更新信息。控制器可以发送更详细的通知,但实际上它不应该/能够知道模型的哪些部分完全改变了。从模型发送更新说明是丑陋的。该模型不仅必须实现消息传递,而且混合通知也会变得非常混乱。控制器可以通过使用主题来划分更新通知和其他一些。例如。更改视频资源的特定主题。这样片段可以决定他们订阅哪些主题。除此之外,您希望拥有一个可以查询更改值的模型。时间戳等。我有一个应用程序,用户在画布上绘制形状。它们被渲染为位图并在 OpenGL 视图中用作纹理。我当然不想每次在 GLViewFragment 中调用“updateFromModel()”时都重新加载纹理。

    依赖规则:
    可能没有一直受到尊重。如果控制器处理选项卡切换,它可以简单地在 TabHost 上调用“seletTab()”,因此依赖于外圈。你可以把它变成一个消息,但它仍然是一个逻辑依赖。如果控制器部分必须组织视图的某些元素(通过 image-gallery-fragmen-tab 加载图像后自动显示 image-editor-fragment-tab),则无法完全避免依赖关系。也许你可以通过建模视图状态来完成它,并让你的视图部分从 viewstate.currentUseCase 或类似的东西中组织起来。但是,如果您需要对应用程序的视图进行全局控制,我会说这个依赖规则会出现问题。如果您尝试保存一些数据并且您的模型要求覆盖权限怎么办?您需要为此创建某种 UI。再次依赖。您可以向视图发送消息并希望 DialogFragment 接收它。如果它存在于您链接中描述的高度模块化的世界中。

    实体:
    是我方法中的模型类。这与您提供的链接非常接近。

    用例:
    我现在没有明确建模的那些。 Atm 我正在为视频游戏资产进行编辑。在一个片段中绘制形状,在另一个片段中应用着色值,在画廊片段中保存/加载,在另一个片段中导出到纹理图集......类似的东西。我会将用例添加为某种请求子集。基本上,用例作为一组规则,请求允许/需要/预期/禁止等顺序。我会像事务一样构建它们,以便用例可以继续进行,可以完成,可以取消甚至滚动背部。例如。用例将定义保存新绘制图像的顺序。包括发布一个对话框来请求覆盖权限,并在未授予权限或超时时回滚。但是用例以许多不同的方式定义。有些应用程序只有一个用例来进行一小时的活跃用户交互,有些应用程序有 50 个用例只是为了从 atm 取钱。 ;)

    接口适配器:
    这里有点复杂。对我来说,这对于安卓应用来说似乎是非常高的水平。它声明“接口适配器环包含 GUI 的整个 MVC 架构”。我无法真正解决这个问题。也许您正在构建比我复杂得多的应用程序。

    框架和驱动程序:
    不知道该怎么想这个。 “网络是一个细节,数据库是一个细节......”并且图形中也包含这个环中的“UI”。我的小脑袋太多了

    让我们检查其他“断言”
    独立于框架。该架构不依赖于某些功能丰富的软件库的存在。这使您可以将此类框架用作工具,而不必将系统塞进其有限的约束中。
    嗯,好吧,如果您运行自己的架构,这就是您所得到的。

    可测试。无需 UI、数据库、Web 服务器或任何其他外部元素即可测试业务规则。
    在我的方法中,模型类既不知道控制器或视图也不知道消息传递。可以仅使用这些类来测试状态一致性。

    独立于 UI。 UI 可以轻松更改,而无需更改系统的其余部分。例如,可以用控制台 UI 替换 Web UI,而无需更改业务规则。
    对android来说又有点矫枉过正,不是吗?独立是的。在我的方法中,您可以添加或删除片段,只要它们不需要在更高的地方进行显式处理。但是用控制台 UI 替换 Web UI 并让系统像以前一样运行是架构怪胎的梦想。一些 UI 元素是所提供服务的组成部分。当然,我可以轻松地将画布绘图片段换成控制台绘图片段,或者将经典照片片段换成“使用控制台拍照”片段,但这并不意味着应用程序仍然可以工作。从技术上讲,它在我的方法中很好。如果你实现了一个 ascii 控制台视频播放器,你可以在那里渲染视频,应用程序的其他部分不需要关心。但是,可能是控制器支持的请求集与新的控制台 UI 不一致,或者用例的设计不符合需要通过控制台界面访问视频的顺序。视图并不总是许多架构大师喜欢将其视为不重要的呈现奴隶。

    独立于数据库。您可以将 Oracle 或 SQL Server 换成 Mongo、BigTable、CouchDB 或其他东西。您的业​​务规则未绑定到数据库。
    所以啊?这与您的架构有什么直接关系?使用正确的适配器和抽象,您可以在 hello world 应用程序中使用它。

    独立于任何外部机构。事实上,您的业务规则根本不了解外部世界。
    同样在这里。如果您想要模块化的独立代码,请编写它。很难说具体的。

    【讨论】:

      【解决方案2】:

      发布/订阅

      通常,对于 n:m 通信(n 个发送者可能会向 m 个接收者发送消息,而所有发送者和接收者彼此不认识),您将使用 publish/subscribe pattern。 有很多库实现了这种通信方式,例如对于 Java,有一个 EventBus implementation in the Guava library。 对于应用内通信,这些库通常称为 EventBus 或 EventManager 并发送/接收事件

      领域事件

      假设您现在创建了一个事件VideoRatedEvent,它表示用户喜欢或不喜欢视频。 这些类型的事件称为Domain Events。事件类是一个简单的 POJO,可能如下所示:

      class VideoRatedEvent {
          /** The video that was rated */
          public Video video;
          /** The user that triggered this event */
          public User user;
          /** True if the user liked the video, false if the user disliked the video */
          public boolean liked;
      }
      

      调度事件

      现在,每当您的用户喜欢或不喜欢某个视频时,您都需要发送VideoRatedEvent。 使用 Guava,您只需将实例化的事件对象传递给对象EventBus.post(myVideoRatedEvent)。 理想情况下,事件在您的域对象中生成并在持久事务中分派(有关详细信息,请参阅this blog post)。 这意味着当你的领域模型状态被持久化时,事件就会被分派。

      事件监听器

      在您的应用程序中,受事件影响的所有组件现在都可以侦听域事件。 在您的特定示例中,VideoDetailViewStatsRepository 可能是VideoRatedEvent 的事件侦听器。 当然,您需要使用EventBus.register(Object) 将它们注册到 Guava EventBus。

      【讨论】:

      • 我发现在不同的编码环境中寻找类似的解决方案通常会导致找到问题的简单答案。例如,所讨论的问题正是像敲除、主干等库在 js 中为浏览器应用程序解决的问题。解决方案就是上面答案中提到的那个。
      猜你喜欢
      • 2016-04-24
      • 2020-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-09
      • 2020-02-03
      • 2019-06-20
      • 1970-01-01
      相关资源
      最近更新 更多