问题描述
我的代码如下:
class Synchronization
def initialize
end
def perform
detect_outdated_documents
update_documents
end
private
attr_reader :documents
def detect_outdated_documents
@documents = DetectOutdatedDocument.new.perform
end
def update_documents
UpdateOutdatedDocument.new(documents).perform
end
@documents
是我从 DetectOutdatedDocument
中的方法返回的哈希数组。
然后我使用这个 Hash 数组来初始化 UpdateOutdatedDocument
类并运行 perform 方法。
这样的事情正确吗? 还是应该使用关联或其他方式?
解决方法
Ruby 到 UML 的映射
我不是 Ruby 专家,但鉴于其 syntax,我从您的代码段中了解到:
- 有一个 Ruby 类
Synchronization
:那是一个 UML 类 - Ruby 类有 4 个方法
initialize
、perform
、detect_outdated_documents
和update_documents
,最后两个是私有的。这将是 4 个 UML 操作。 -
initialize
是 constructor,因为它是空的,所以你没有在你的 UML 类图中提到它,没关系。 - Ruby 类有 1 个实例变量
@documents
。在 UML 中,这将是关联端的属性或角色。 - Ruby 类有一个使用
attr_reader
创建的 getter。但由于它在一个私有部分,它的可见性应该是-
。此 other answer 解释了如何在 UML 中优雅准确地使用 getter 和 setter(非常感谢 @engineersmnky 对 Ruby 中 getter 的解释,并纠正了我最初在这方面的误解) - 我知道
SomeClass.new
在 Ruby 中创建了一个类SomeClass
的新对象。
Ruby 和 UML 中的动态类型
UML 类图基于明确定义的类型/类。您通常会仅与已知的类确定关联、聚合和组合,这些类肯定与这些类有稳定的关系。 Ruby 是动态类型的,关于实例变量的所有已知信息是它的类型为 Object
,这是 Ruby 中可能的最高泛化。
此外,Ruby 方法返回其执行路径中最新语句/表达式的值。如果您不关心对象的返回值,只需将其标记为 Object
(感谢 engineersmnky 的解释)。
补充说明:
- UML 中没有
void
类型(另请参阅此 SO question)。不返回任何内容的 UML 操作只是一个没有指示返回类型的操作。 - 还请记住,使用不属于 UML 标准的类型(例如
Array
、Hash
、Object
、...)将假设使用特定于语言的 UML profile。
基于所有这些,并考虑到数组也是一个 Object
,您的代码将导致一个非常简单的 UML 图,有 3 个类,它们都是 Object
的特化,以及一个Synchronization
和 Object
之间的一对多关联,角色 @documents
位于 Object
端。
这一切都是我们所希望的吗?
非常通用的类图,也许可以很好地匹配实现。但它可能无法准确代表设计。
您有权在 UML 中对独立于实现的设计进行建模。因此,如果实例变量的类型是设计已知的(例如,您希望它是某种类型,并通过初始化和 API 设计确保该类型将被强制执行),即使它偏离了代码:
- 您已经完成了一些手动类型推断来推断 UML 操作的返回类型。由于所有 Ruby 方法都会返回某些内容,因此我们希望所有 Ruby 方法至少有一个
Object
返回类型。但是您可以不指明任何返回类型(相当于void
的 UML)来表示返回值并不重要。 - 您还对实例变量(UML 属性)进行了一些类型推断:您阐明了它可以采用的唯一值是
DetectOutdatedDocument.new.perform
返回的值。 - 您的图表表明该类与未指定数量的
DetectOutdatedDocument
对象相关,我们猜测这是因为@documents
的可能值。并且该属性表示为对象数组。在图表上同时显示两者是非常误导的。所以我建议删除document
属性。相反,更喜欢在document
一侧的关联端使用DetectOutdatedDocument
角色。对于非 Ruby 本地读者来说,这将极大地阐明为什么图表上有第二个类。 :-)(花了我一段时间) - 现在您不应该使用黑色菱形进行构图。因为
documents
有一个公共阅读器;因此其他对象也可以分配给相同的文档。由于 Ruby 似乎对对象具有引用语义,因此副本将引用相同的对象。这充其量是共享聚合(白色菱形)。而且由于 UML 没有很好地定义聚合语义,您甚至可以显示一个简单的关联。
最后一点:根据您显示的代码,我们无法确认 UpdateOutdatedDocument
和 DetectOutdatedDocument
之间存在聚合。如果你确定有这样的关系,你可以保留它。但是,如果它仅基于您向我们展示的代码段,请删除聚合关系。您最多可以显示使用依赖性。但通常在 UML 中,如果它是关于方法体的,你不会显示这样的依赖关系,因为操作可以非常不同地实现,而不必具有这种依赖关系。
在发布的代码中没有关系,无论是 UML 还是其他。事实上,乍一看它可能看起来就像一个 Synchronization has-many @documents,但变量及其内容从未定义、初始化或分配。
如果这是家庭作业,您可能需要询问您的导师目标是什么,正确答案应该是什么。如果它是一个真实世界的项目,那么您还没有做到以下几点:
- 定义了像 Document 这样的合作者对象
- 以同步类可访问的方式初始化@documents
- 允许你的类方法接受任何依赖注入
如果没有列出至少一项,您的 UML 图就不能真正适合发布的代码。