也许并不是你问题的答案,但是仍有值得思考的地方。
我对于你在组件层次结构中放置数据访问的位置持有异议。我不会把它放在两个功能域层之间,甚至不会放在单个领域模型类的“上面”。数据访问或持久性不是任何领域类的关注点。它应该只是可以对它们进行操作的东西,而不是它们所做的事情。
尽管我最初编写了像TClient.Save和TClient.Load这样的代码,但现在我得出的结论是并不是客户端决定需要保存,而是用户交互决定何时需要加载领域实例的数据,以及是否应该持久化客户端的数据。因此,我现在支持在GUI中(更具体地说是控制器中)编写DataStore.Load(ClientInstance)和DataStore.Save(ClientInstance)等内容。然后由数据访问层来确定如何执行此操作。它可以使用C#中的反射或Delphi中的新RTTI来迭代所有客户端属性,以便将它们发送到数据库。
虽然分层是一个非常好的概念,可以分离关注点,并通过简单地遵循“您可以向下调用但不能向上调用”来防止将东西放置在各个地方,但是当涉及日志记录、异常处理、通知和所有其他有趣的交叉关注点时,它并没有帮助太多。
此外,由于公共层是一个实用程序层,因此确实应该对所有其他层可访问。
为了将所有内容放在一张图片中(其中我保留了你在简单领域类、模型和跨类业务规则之间所做的区分):
+---+ +-------------+
| C |<--| Data Access |<--------------------------+
| o | +-------------+ |
| m | | |
| m | | |
| o | v |
| n | +-------------+ +----------------+ +-----+
| |<--| Model +<--| Cross class |<--| GUI |
| | +-------------+ | business rules | | |
| | | | | |
| |<--------------------| | | |
| | +----------------+ | |
| | | |
| |<-----------------------------------------| |
+---+ +-----+
当前调用数据库的INotify实现位于模型中,如上图所示,它并不直接调用数据访问层,而是被数据访问层调用或者说被查询。
问题在于,INotify应该放在“模型”中,即域层的一部分,还是应该作为通用接口,并且应该有一个单独的“通知”层/组件,可以从域和GUI访问。这个新组件不仅可以关注通知,还可以关注许多其他交叉问题,例如日志记录。它可以访问公共(当然)和数据访问组件以及GUI,至少以某种回调方式。
在下面的图片中,我试图将其可视化,但我不太擅长可视化,并且总是遇到那些讨厌的交叉问题。这就是为什么从域层到交叉问题没有调用箭头,尽管域层当然应该能够访问例如“Logger”接口。也许我试图过于区分公共和交叉组件,可以提出将它们合并,并将它们可视化为“实用程序”层/组件内的单独块。
+--------------------------------------------+
+-----| Cross cutting concerns |
| +--------------------------------------------+
v v^ ^
+---+ +-------------+ |
| C |<--| Data Access |<--------------------------+ |
| o | +-------------+ | |
| m | | | |
| m | | | |
| o | v | v
| n | +-------------+ +----------------+ +-----+
| |<--| Model +<--| Cross class |<--| GUI |
| | +-------------+ | business rules | | |
| | | | | |
| |<--------------------| | | |
| | +----------------+ | |
| | | |
| |<-----------------------------------------| |
+---+ +-----+