门面设计模式使用关联或聚合关系?

3

我正在学习GoF设计模式,特别是外观模式。我理解其用途和实现,但我对其UML模型有疑问。 我的教授提出的解决方案可以概括如下:

public class Facade{
    private ClassA c1;
    private ClassB c2;
    private ClassC c3;

    public Facade(){
        this.c1 = new ClassA;
        this.c2 = new ClassB;
        this.c3 = new ClassC;
    }

    public void FacadeMethod(){
        ...
        c1.operationA();
        c2.operationB();
        c3.operationC();
        ...
    }

}

所提出的UML模型如下: Facade UML Model 外观类(Facade Class)与ClassA、ClassB、ClassC类有关联关系。但是,这些应该是聚合关系吗?外观类(Facade Class)具有对ClassA的引用c1,对ClassB的引用c2和对ClassC的引用c3,因此我认为这是一种“拥有”关系。您有什么想法?
3个回答

4

初步说明

许多来源倾向于使用UML-aggregation来图形化表示对象组合。一个鼓励这种趋势的流行来源是例如wikipedia。然而,这并不可推荐。

您的教授在符号和外观示例方面(几乎)是正确的

您的教授在外观实现代码中使用了对象组合,并用可导航关联来表示,这是正确的。有些专家认为,更好的做法是使用关联端所有权的点符号表示。

您的教授使用对象组合和向前的外观调用对象。这是一种有效的外观实现:

  • GoF明确指出在第187页上,“[facade]将客户端请求委托给适当的子系统对象”,这清楚地允许对象组合。

  • 虽然这不是实现外观最常用的方式(通常使用类方法),但GoF在第188页进一步描述了实现替代方案:

    一个替代子类化 [抽象外观类] 的方法是,使用不同的子系统对象配置外观对象。要自定义外观,只需替换一个或多个其子系统对象即可。


额外参数

你能用UML聚合来表示对象组合吗?

表面上看,使用UML聚合来建模对象组合似乎并没有根本性的错误:UML并没有很好地定义聚合语义,留下了解释的余地。在UML规范的第110页上解释道:

  • 聚合是指一个实例被用来“将一组实例组合在一起”——但没有什么禁止这个集合只有一个成员。
  • 共享聚合的确切语义(也称为白色菱形)因应用领域和建模者而异”,所以为什么不(误)用它来表示对象组合呢?

虽然这是一个有效的解释,但它也存在一些缺陷:

  • 许多建模者可能会误解聚合具有默认的 * 倍数,因为“一组实例”的措辞,而集合不是默认的单例。当 UML 聚合用于对象组合时,应明确表示 1 的倍数以避免误解。

  • 仔细阅读第110页后可以发现,聚合实际上旨在模拟部分-整体关系。在其他情况下使用它进行对象组合因此是对 UML 聚合的误用(不是错误,但不是意图):

    属性具有聚合属性(...); 表示整个组的实例由属性的所有者分类,而表示分组个体的实例则由属性的类型分类。

  • 这种对象组的解释在第198页得到了加强,认为 UML 复合聚合和共享聚合之间的主要区别在于聚合项的所有权:

    二元关联可以表示复合聚合(即整体/部分关系)。

  • UML 的创始人 Booch、Rumbaugh 和 Jacobson 在他们的非规范但更易读的书《UML 用户指南》中确认了这一点:

    (...) 聚合表示“具有”关系,这意味着整体对象具有部分对象。聚合实际上只是一种特殊类型的关联,并通过在整体端点处加上未填充的菱形来指定。

考虑到这种语义的薄弱性,我们可以总结:UML聚合可以使用对象组合来实现。但并非所有对象组合都实现了UML聚合。两个概念之间没有一对一的映射关系。
你是否应该使用聚合呢?我们可以得出马丁·福勒在他的优秀书籍《UML精简》中的引用,他在分析独立于任何实现考虑因素的聚合和普通关联之间差异的难度时指出:
“聚合是严格无意义的;因此,我建议您在自己的图表中忽略它。如果您在其他人的图表中看到它,您需要深入挖掘以找出他们的意思。不同的作者将其用于不同的目的。”

关于多重性,通常在缺失时被认为是1(我在BoUML中遵循这个(非)规则进行代码生成),但在第202页的顶部,规范说“注意。如果在图表上没有显示多重性,则不能得出有关模型中多重性的结论”。因此,认为聚合的多重性为“*”与认为非聚合的多重性为1一样滥用。 - bruno
但是“图表上没有显示多重性”可以有几种理解方式,可能是由于绘图设置而未显示多重性,或者可能是在聚合的定义中未指定多重性。规范谈论图表时会让人感到困扰。对于这个新的好答案表示赞赏。 - bruno
1
@bruno 是的,我完全同意:认为 * 或 1 是滥用的,因为未指定就是未指定。解释应该始终谨慎并自担风险 :-) UML标准本应受到启发,明确给出“具有”语义的聚合(Booch符号在他的UML之前的符号中提供了这种语义,OMT也有,并且甚至使用白色菱形表示它):这将非常优雅地为聚合和对象组合提供支持。 - Christophe
1
@sixpain 这就是我所说的关于误用 UML 聚合符号表示对象组合的意思:Java 对象具有引用语义。在 Java 中,对象组合与拥有对象引用相同。虽然白色菱形可以被辩护,但在没有部分/整体关系的情况下,这不是一个好的实践。这种做法非常古老:在 UML 之前的 OMT 中使用白色菱形表示“具有”,许多人将其用于对象组合。部分/整体关系出现得比较晚,当 OMT 和 Booch 与 OOSE 合并成 UML 时才出现。 - Christophe
1
@sixpain 有趣的是,GoF(使用OMT符号而非UML)在适配器中未使用菱形。但他们的用法也不一致。他们在桥接器、策略和装饰器中使用对象组合语义,并在组合体、享元、状态和解释器中使用聚合语义。在命令和备忘录中使用含糊语义。在其他使用组合或聚合的模式中则不使用它。我编辑了马丁·福勒的一段话,建议如何使用UML聚合。 - Christophe
显示剩余2条评论

3

但这些应该是聚合关系吗?

不是的,一个外观模式不是由ClassA、ClassB和ClassC组成的,即这些类不是外观模式的部分。


谢谢!这种情况和(例如)适配器模式有什么区别?在适配器模式中,适配器类与被适配类具有聚合关系,因为适配器引用了被适配对象。也许我误解了Java中的聚合含义。 - sixpain
@sixpain 对我来说,使用聚合来实现适配器模式是错误的。聚合的典型用法是 Car <>---> Engine,因为汽车由(至少)一个发动机组成。适配器需要并使用被适配者,但不是由它组成的。 - bruno
我同意你的观点,但在维基百科、我的书和我的教授的模型中,都使用了聚合关系。看到Java实现后,我唯一能想到的解释是Adapter具有对Adaptee对象的引用,因此Adapter“拥有”Adaptee。https://it.wikipedia.org/wiki/Adapter_pattern#/media/File:Adapter_using_delegation_UML_class_diagram.svg - sixpain
@sixpain 一个实现可以表示一个组合(至少在C++中,不确定在Java中),但永远不能表示一个聚合。 - bruno

1

聚合是关联的一种更具体形式,所以教授的解决方案并没有错,但我认为你的方案也可以。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接