Clean Architecture:如何在 UI 中反映数据层的变化
Clean Architecture: How to reflect the data layer's changes in the UI
我正在尝试根据 Android 中的 Uncle Bob's Clean Architecture 进行设计。
问题:
我想解决的是如何使一个存储库中生成的更改反映在应用程序的其他部分,如其他存储库或视图。
例子
我为这个例子设计了一个非常简化的例子。请注意边界接口已被删除以保持图表较小。
想象一个显示视频列表(带有标题、缩略图和点赞数)的应用程序,点击一个视频您可以看到详细信息(在那里您可以 like/dislike 视频)。
此外,该应用程序还有一个统计系统,可以计算用户喜欢或不喜欢的视频数量。
此应用的主要 类 可能是:
对于视频 part/module:
对于统计数据 part/module:
目标
现在假设您检查自己的统计数据,然后浏览视频列表,打开其中一个的详细信息,然后点击“赞”按钮。
点赞发送到服务器后,应用程序中有几个元素需要注意变化:
- 当然,详细视图应该随着更改而更新(这可以通过回调实现,所以没问题)
- 视频列表应更新给定视频的 "likes" 计数
StatsRepository
可能想在投票新视频后 update/invalidate 缓存
- 如果统计列表可见(想象一个分屏)它也应该显示更新的统计(或者至少接收 re-query 数据的事件)
问题
解决这种通信的常用模式有哪些?
请尽可能完整地回答您的问题,具体说明事件的生成位置、它们如何通过应用传播等。
注意:完成答案将给予奖励
发布/订阅
通常,对于 n:m 通信(n 个发送者可以向 m 个接收者发送消息,而所有发送者和接收者彼此不认识),您将使用 publish/subscribe pattern。
有很多库实现了这种通信方式,例如 Java 有一个 EventBus implementation in the Guava library。
对于应用内通信,这些库通常称为 EventBus 或 EventManager 和 send/receive events.
领域事件
假设您现在创建了一个事件 VideoRatedEvent
,它表示用户喜欢或不喜欢某个视频。
这些类型的事件被称为 Domain Events。事件 class 是一个简单的 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)。
这意味着当您的域模型状态被持久化时,事件被调度。
事件侦听器
在您的应用程序中,受事件影响的所有组件现在都可以侦听域事件。
在您的特定示例中,VideoDetailView
或 StatsRepository
可能是 VideoRatedEvent
的事件侦听器。
当然,您需要使用 EventBus.register(Object)
.
将它们注册到 Guava EventBus
这是我个人的 5cents,可能与您的示例 "The Clean Architecure" 的相关性不够密切。
我通常尝试在 android 的活动和片段上强制使用一种 MVC,并使用 publish/subscribe 进行通信。作为组件,我有模型 classes 来处理业务逻辑和数据状态。它们的数据更改方法只能由控制器 classes 调用,通常是 activity class 并且还处理会话状态。我使用片段来管理应用程序的不同视图部分和这些片段下的视图(很明显)。所有片段都订阅一个或多个主题。我使用我自己的简单 DataDistributionService 来处理不同的主题,从注册的发布者那里获取消息并将它们转发给所有订阅者。 (部分受 OMGs DDS 的影响,但更原始)一个简单的应用程序只有一个主题,例如"Main".
视图交互的每个部分(触摸等)首先由其片段处理。该片段可能会在不发送通知的情况下更改一些内容。例如。如果应用程序的其余部分不需要 know/react,则切换呈现数据元素的子范围。否则,该片段会向 DDS 发布包含必要参数的 ViewRequest(...)。
DDS 广播该消息并在某个时候到达控制器。这可以只是主要的 activity 或特定的控制器实例。应该只有一个控制器,以便只处理一次请求。控制器基本上有一长串请求处理代码。当请求到达时,控制器调用模型中的业务逻辑。控制器还处理其他与视图相关的事情,例如安排视图(选项卡)或启动用户输入对话框(覆盖文件?)以及模型不应该知道但会影响的其他事情(Throw new NoOverWritePermissionException())
模型更改完成后,控制器决定是否必须发送更新通知。 (通常是这样)。这样模型 classes 就不需要监听或发送消息,只需要处理业务逻辑和一致的状态。更新通知由 运行 "updateFromModel()".
的片段广播和接收
效果:
命令是全局的。可以从可以访问 DDS 的任何地方发送任何 ViewRequest 或其他类型的请求。片段不必提供侦听器 class 并且没有更高的实例必须为其实例化片段实现侦听器。如果新片段不需要新请求,则可以在不对控制器进行任何更改的情况下添加它 classes.
模型 classes 根本不需要了解通信。保持一致的状态和处理所有数据管理可能已经够难了。不需要消息处理或会话状态处理。但是,模型可能无法防止来自视图的恶意调用。但这是一个普遍的问题,如果模型必须在某个时候给出参考,就无法真正避免。如果您的应用适用于仅传递 copies/flat 数据的模型,则有可能。但在某些时候,ArrayAdapter 只需要访问他应该在 gridview 中绘制的位图。如果你买不起副本,你总是有 "view makes a changing call to the model" 的风险。不一样的战场...
更新调用可能太简单了。如果片段的更新很昂贵(OpenGL 片段重新加载纹理...),您希望获得更详细的更新信息。控制器可以发送更详细的通知,但它实际上不应该 to/be 能够知道模型的哪些部分发生了确切的变化。从模型发送更新说明是丑陋的。该模型不仅必须实现消息传递,而且混合通知也会变得非常混乱。控制器可以通过使用主题来划分更新通知和其他一些。例如。更改视频资源的特定主题。这样片段可以决定他们订阅哪些主题。除此之外,您还希望拥有一个可以查询更改值的模型。时间戳等 我有一个应用程序,用户可以在 canvas 上绘制形状。它们被渲染为位图并用作 OpenGL 视图中的纹理。我当然不想每次在 GLViewFragment 中调用 "updateFromModel()" 时都重新加载纹理。
依赖规则:
可能一直没有受到尊重。如果控制器处理选项卡切换,它可以简单地在 TabHost 上调用 "seletTab()",因此依赖于外圈。你可以把它变成一条消息,但它仍然是一个逻辑依赖。如果控制器部分必须组织视图的某些元素(通过 image-gallery-fragment-tab 加载图像后自动显示 image-editor-fragment-tab),则无法完全避免依赖性。也许您可以通过对视图状态建模来完成它,并让您的视图部分从 viewstate.currentUseCase 或类似的东西组织起来。但是如果你需要对你的应用程序的视图进行全局控制,你会遇到我说的这个依赖规则的问题。如果您尝试保存一些数据并且您的模型要求覆盖权限怎么办?您需要为此创建某种 UI。再次依赖。您可以向视图发送消息并希望 DialogFragment 接收它。如果它存在于您 link.
中描述的极其模块化的世界中
实体:
是我方法中的模型 classes。这与您提供的 link 非常接近。
用例:
我现在没有明确建模的那些。 Atm 我正在从事视频游戏资产的编辑工作。在一个片段中绘制形状,在另一个片段中应用着色值,在 galleryfragment 中 saving/loading,在另一个片段中导出到纹理图集……诸如此类。我会将用例添加为某种请求子集。基本上,一个用例作为一组规则,其中请求的顺序是 allowed/required/expected/forbidden 等。我会像事务一样构建它们,以便用例可以继续进行,可以完成,可以取消,甚至可以回滚。例如。用例将定义保存新绘制图像的顺序。包括发布一个对话框以请求覆盖权限并在未授予权限或达到超时时回滚。但是用例以许多不同的方式定义。一些应用程序只有一个用例用于一个小时的活跃用户交互,一些应用程序有 50 个用例只是为了从 atm 取钱。 ;)
接口适配器:
这里有点复杂。对我来说,这似乎是 android 应用程序的极高水平。它表示 "The Ring of Interface Adapters contains the whole MVC architecture of a GUI"。我真的不能全神贯注于此。也许您构建的应用程序比我复杂得多。
框架和驱动程序:
不知道怎么看这个。 "The web is a detail, the database is a detail..." 并且图形也包含此环中的 "UI"。对我的小脑袋来说太多了
让我们检查另一个 "asserts"
独立于框架。该体系结构不依赖于某些功能丰富的软件库的存在。这使您可以将此类框架用作工具,而不必将您的系统塞入它们的有限约束中。
嗯,是的,如果你 运行 你自己的架构就是你得到的。
可测试。无需 UI、数据库、Web 服务器或任何其他外部元素即可测试业务规则。
在我的方法模型中,classes 既不知道控制器或视图,也不知道消息传递。可以单独使用那些 classes 来测试状态一致性。
独立于UI。 UI 可以轻松更改,而无需更改系统的其余部分。例如,可以在不更改业务规则的情况下,将 Web UI 替换为控制台 UI。
android 又有点矫枉过正了不是吗?独立是的。在我的方法中,您可以添加或删除片段,只要它们不需要在更高的地方进行显式处理即可。但是用控制台 UI 替换 Web UI 并像以前一样拥有系统 运行 是架构怪胎的梦想。一些 UI 元素是所提供服务的组成部分。当然,我可以轻松地将 canvas 绘图片段替换为控制台绘图片段,或者将 classic 照片片段替换为 'take picture with console' 片段,但这并不意味着应用程序仍然有效。从技术上讲,我的方法很好。如果您实施 ascii 控制台视频播放器,您可以在那里呈现视频,应用程序的其他部分不一定会关心。然而,可能是控制器支持的请求集与新控制台不一致 UI 或者用例不是为需要通过控制台界面访问视频的顺序设计的。视图并不总是许多架构专家喜欢将其视为不重要的呈现奴隶。
独立于数据库。您可以换出 Oracle 或 SQL 服务器,换成 Mongo、BigTable、CouchDB 或其他东西。您的业务规则未绑定到数据库。
所以啊?这与您的架构有何直接关系?使用正确的适配器和抽象,您可以在 hello world 应用程序中使用它。
独立于任何外部机构。事实上,您的业务规则根本不知道外界的任何信息。
同样在这里。如果你想要模块化的独立代码,那就写吧。很难说具体的事情。
我正在尝试根据 Android 中的 Uncle Bob's Clean Architecture 进行设计。
问题:
我想解决的是如何使一个存储库中生成的更改反映在应用程序的其他部分,如其他存储库或视图。
例子
我为这个例子设计了一个非常简化的例子。请注意边界接口已被删除以保持图表较小。
想象一个显示视频列表(带有标题、缩略图和点赞数)的应用程序,点击一个视频您可以看到详细信息(在那里您可以 like/dislike 视频)。
此外,该应用程序还有一个统计系统,可以计算用户喜欢或不喜欢的视频数量。
此应用的主要 类 可能是:
对于视频 part/module:
对于统计数据 part/module:
目标
现在假设您检查自己的统计数据,然后浏览视频列表,打开其中一个的详细信息,然后点击“赞”按钮。
点赞发送到服务器后,应用程序中有几个元素需要注意变化:
- 当然,详细视图应该随着更改而更新(这可以通过回调实现,所以没问题)
- 视频列表应更新给定视频的 "likes" 计数
StatsRepository
可能想在投票新视频后 update/invalidate 缓存- 如果统计列表可见(想象一个分屏)它也应该显示更新的统计(或者至少接收 re-query 数据的事件)
问题
解决这种通信的常用模式有哪些? 请尽可能完整地回答您的问题,具体说明事件的生成位置、它们如何通过应用传播等。
注意:完成答案将给予奖励
发布/订阅
通常,对于 n:m 通信(n 个发送者可以向 m 个接收者发送消息,而所有发送者和接收者彼此不认识),您将使用 publish/subscribe pattern。 有很多库实现了这种通信方式,例如 Java 有一个 EventBus implementation in the Guava library。 对于应用内通信,这些库通常称为 EventBus 或 EventManager 和 send/receive events.
领域事件
假设您现在创建了一个事件 VideoRatedEvent
,它表示用户喜欢或不喜欢某个视频。
这些类型的事件被称为 Domain Events。事件 class 是一个简单的 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)。
这意味着当您的域模型状态被持久化时,事件被调度。
事件侦听器
在您的应用程序中,受事件影响的所有组件现在都可以侦听域事件。
在您的特定示例中,VideoDetailView
或 StatsRepository
可能是 VideoRatedEvent
的事件侦听器。
当然,您需要使用 EventBus.register(Object)
.
这是我个人的 5cents,可能与您的示例 "The Clean Architecure" 的相关性不够密切。
我通常尝试在 android 的活动和片段上强制使用一种 MVC,并使用 publish/subscribe 进行通信。作为组件,我有模型 classes 来处理业务逻辑和数据状态。它们的数据更改方法只能由控制器 classes 调用,通常是 activity class 并且还处理会话状态。我使用片段来管理应用程序的不同视图部分和这些片段下的视图(很明显)。所有片段都订阅一个或多个主题。我使用我自己的简单 DataDistributionService 来处理不同的主题,从注册的发布者那里获取消息并将它们转发给所有订阅者。 (部分受 OMGs DDS 的影响,但更原始)一个简单的应用程序只有一个主题,例如"Main".
视图交互的每个部分(触摸等)首先由其片段处理。该片段可能会在不发送通知的情况下更改一些内容。例如。如果应用程序的其余部分不需要 know/react,则切换呈现数据元素的子范围。否则,该片段会向 DDS 发布包含必要参数的 ViewRequest(...)。
DDS 广播该消息并在某个时候到达控制器。这可以只是主要的 activity 或特定的控制器实例。应该只有一个控制器,以便只处理一次请求。控制器基本上有一长串请求处理代码。当请求到达时,控制器调用模型中的业务逻辑。控制器还处理其他与视图相关的事情,例如安排视图(选项卡)或启动用户输入对话框(覆盖文件?)以及模型不应该知道但会影响的其他事情(Throw new NoOverWritePermissionException())
模型更改完成后,控制器决定是否必须发送更新通知。 (通常是这样)。这样模型 classes 就不需要监听或发送消息,只需要处理业务逻辑和一致的状态。更新通知由 运行 "updateFromModel()".
的片段广播和接收
效果:
命令是全局的。可以从可以访问 DDS 的任何地方发送任何 ViewRequest 或其他类型的请求。片段不必提供侦听器 class 并且没有更高的实例必须为其实例化片段实现侦听器。如果新片段不需要新请求,则可以在不对控制器进行任何更改的情况下添加它 classes.
模型 classes 根本不需要了解通信。保持一致的状态和处理所有数据管理可能已经够难了。不需要消息处理或会话状态处理。但是,模型可能无法防止来自视图的恶意调用。但这是一个普遍的问题,如果模型必须在某个时候给出参考,就无法真正避免。如果您的应用适用于仅传递 copies/flat 数据的模型,则有可能。但在某些时候,ArrayAdapter 只需要访问他应该在 gridview 中绘制的位图。如果你买不起副本,你总是有 "view makes a changing call to the model" 的风险。不一样的战场...
更新调用可能太简单了。如果片段的更新很昂贵(OpenGL 片段重新加载纹理...),您希望获得更详细的更新信息。控制器可以发送更详细的通知,但它实际上不应该 to/be 能够知道模型的哪些部分发生了确切的变化。从模型发送更新说明是丑陋的。该模型不仅必须实现消息传递,而且混合通知也会变得非常混乱。控制器可以通过使用主题来划分更新通知和其他一些。例如。更改视频资源的特定主题。这样片段可以决定他们订阅哪些主题。除此之外,您还希望拥有一个可以查询更改值的模型。时间戳等 我有一个应用程序,用户可以在 canvas 上绘制形状。它们被渲染为位图并用作 OpenGL 视图中的纹理。我当然不想每次在 GLViewFragment 中调用 "updateFromModel()" 时都重新加载纹理。
依赖规则:
可能一直没有受到尊重。如果控制器处理选项卡切换,它可以简单地在 TabHost 上调用 "seletTab()",因此依赖于外圈。你可以把它变成一条消息,但它仍然是一个逻辑依赖。如果控制器部分必须组织视图的某些元素(通过 image-gallery-fragment-tab 加载图像后自动显示 image-editor-fragment-tab),则无法完全避免依赖性。也许您可以通过对视图状态建模来完成它,并让您的视图部分从 viewstate.currentUseCase 或类似的东西组织起来。但是如果你需要对你的应用程序的视图进行全局控制,你会遇到我说的这个依赖规则的问题。如果您尝试保存一些数据并且您的模型要求覆盖权限怎么办?您需要为此创建某种 UI。再次依赖。您可以向视图发送消息并希望 DialogFragment 接收它。如果它存在于您 link.
中描述的极其模块化的世界中
实体:
是我方法中的模型 classes。这与您提供的 link 非常接近。
用例:
我现在没有明确建模的那些。 Atm 我正在从事视频游戏资产的编辑工作。在一个片段中绘制形状,在另一个片段中应用着色值,在 galleryfragment 中 saving/loading,在另一个片段中导出到纹理图集……诸如此类。我会将用例添加为某种请求子集。基本上,一个用例作为一组规则,其中请求的顺序是 allowed/required/expected/forbidden 等。我会像事务一样构建它们,以便用例可以继续进行,可以完成,可以取消,甚至可以回滚。例如。用例将定义保存新绘制图像的顺序。包括发布一个对话框以请求覆盖权限并在未授予权限或达到超时时回滚。但是用例以许多不同的方式定义。一些应用程序只有一个用例用于一个小时的活跃用户交互,一些应用程序有 50 个用例只是为了从 atm 取钱。 ;)
接口适配器:
这里有点复杂。对我来说,这似乎是 android 应用程序的极高水平。它表示 "The Ring of Interface Adapters contains the whole MVC architecture of a GUI"。我真的不能全神贯注于此。也许您构建的应用程序比我复杂得多。
框架和驱动程序:
不知道怎么看这个。 "The web is a detail, the database is a detail..." 并且图形也包含此环中的 "UI"。对我的小脑袋来说太多了
让我们检查另一个 "asserts"
独立于框架。该体系结构不依赖于某些功能丰富的软件库的存在。这使您可以将此类框架用作工具,而不必将您的系统塞入它们的有限约束中。
嗯,是的,如果你 运行 你自己的架构就是你得到的。
可测试。无需 UI、数据库、Web 服务器或任何其他外部元素即可测试业务规则。
在我的方法模型中,classes 既不知道控制器或视图,也不知道消息传递。可以单独使用那些 classes 来测试状态一致性。
独立于UI。 UI 可以轻松更改,而无需更改系统的其余部分。例如,可以在不更改业务规则的情况下,将 Web UI 替换为控制台 UI。
android 又有点矫枉过正了不是吗?独立是的。在我的方法中,您可以添加或删除片段,只要它们不需要在更高的地方进行显式处理即可。但是用控制台 UI 替换 Web UI 并像以前一样拥有系统 运行 是架构怪胎的梦想。一些 UI 元素是所提供服务的组成部分。当然,我可以轻松地将 canvas 绘图片段替换为控制台绘图片段,或者将 classic 照片片段替换为 'take picture with console' 片段,但这并不意味着应用程序仍然有效。从技术上讲,我的方法很好。如果您实施 ascii 控制台视频播放器,您可以在那里呈现视频,应用程序的其他部分不一定会关心。然而,可能是控制器支持的请求集与新控制台不一致 UI 或者用例不是为需要通过控制台界面访问视频的顺序设计的。视图并不总是许多架构专家喜欢将其视为不重要的呈现奴隶。
独立于数据库。您可以换出 Oracle 或 SQL 服务器,换成 Mongo、BigTable、CouchDB 或其他东西。您的业务规则未绑定到数据库。
所以啊?这与您的架构有何直接关系?使用正确的适配器和抽象,您可以在 hello world 应用程序中使用它。
独立于任何外部机构。事实上,您的业务规则根本不知道外界的任何信息。
同样在这里。如果你想要模块化的独立代码,那就写吧。很难说具体的事情。