TOP

UML中的各种关系

各种关系

UML中的各种关系一览表
名称 英文名称 符号 描述 实现方法 耦合强度 举例 关键词 备注
依赖 dependency 1.当类与类之间有使用关系
时就属于依赖关系;2.依赖
不具有“拥有关系”
,而是
一种“相识关系”;3.一个
类的实现需要另一个类的协助
如果类A访问类B的属性或者方法,
或者类A负责实例化类B
,具体地,
被依赖者B是依赖者A的:1.成员
函数返回值、2.形参、3.局部
变量、4.静态方法调用​
★​ 学生使用电脑
驾驶员使用车​
使用关系​  
关联 association 一种常识上的拥有关系,但
双方又在逻辑上各自独立​
​关联者的某数据成员是指向被关
联者的指针或引用
★★​ 老师和学生是
双向关联​
拥有关系
has-a​
细分为:单向关联、
双向关联、
自身关联、多维关联​
聚合 aggregation 整体与部分的关系,且部分
可脱离整体而单独存在
同“关联”​ ★★★​ 汽车和轮胎​ 整体与部分
单独存在
contains-a​
 
组合 composition 整体与部分的关系,但部分
不能离开整体而单独存在
“整体类”的某数据成员存储
了“部分类”的实例
★★★★​ 人和头颅​ 整体与部分
不能单独存在​
 
泛化 generalization 类与类之间的继承关系,一般
与特殊
的关系,is a
类继承​ ★★★★★​ 动物和猫​ 一般与特殊
is-a​
 
实现 implementation 类与接口之间的实现关系​ 接口实现​ ★★★★★​ 鸡和鸭都实现
下蛋接口​
   

联系与区别

依赖与关联

  • 实现不同:依赖者不会将被依赖者作为自己的数据成员,但关联会。​
  • 侧重点不同:​依赖强调使用,关联强调拥有
  • 关系产生/结束时机不同:依赖关系是仅当使用关系发生(如类A的成员函数以类B的实例为参数)时而产生,伴随着使用结束而结束。关联关系当A中指向B的指针/引用被赋值时产生,当A的实例销毁时关系结束。​

关联与聚合

  • 聚合关系是关联关系的一种,是强的关联关系。
  • 逻辑关系/层次不同:关联关系所涉及的两个类处在同一个层次上,而聚合关系中,两个类处于不同的层次上,一个代表整体,一个代表部分。
  • 指向被关联/聚合者的指针/引用的初始化/赋值形式不同(这一点并非绝对):对于关联,通过A的成员函数( void A::setB(B *pB) )来为A中指向B的指针/引用赋值;对于聚合,通过带参的构造函数( A::A(B *pb) )来初始化A中指向B的指针/引用。​
  • 联和聚合在语法上无法区分,必须考察具体的逻辑关系。

聚合与组合

  • 整体与部分的生命周期不同:对于聚合,整体与部分的生命周期相互独立;对于组合,整体与部分有相同的生命周期,其中代表整体的对象负责代表部分的对象的生命周期。​
  • 构造函数不同:聚合类的构造函数中包含另一个类的实例作为参数,组合类的构造函数包含另一个类的实例化
  • 信息的封装性不同:聚合关系中,客户端可以同时了解“整体类”与“部分类”;组合关系中,客户端只认识“整体类”,“部分类”被严密封装在“整体类中”,对客户端不可见。

举例说明

下图是设计模式中命令模式(Command Pattern)的UML类图和C++实现,其中代码“改编”自这个开源项目。不熟悉命令模式的读者请自行学习。​

 1 #include <iostream>
 2 
 3 class Receiver
 4 {
 5 public:
 6     void Action()
 7     {
 8         std::cout << "Receiver: execute action" << std::endl;
 9     }
10     // ...
11 };
12 
13 class Command
14 {
15 public:
16     virtual ~Command() {}
17     virtual void Execute() = 0;
18 
19 protected:
20     Command() {}
21 };
22 
23 class ConcreteCommand : public Command
24 {
25 public:
26     ConcreteCommand(Receiver *r) : receiver(r) {}
27 
28     ~ConcreteCommand()
29     {
30         if (receiver)
31         {
32             delete receiver;
33         }
34     }
35 
36     void Execute()
37     {
38         receiver->Action();
39     }
40 
41 private:
42     Receiver *receiver;
43 };
44 
45 class Invoker
46 {
47 public:
48     void SetCommand(Command *c)
49     {
50         command = c;
51     }
52 
53     void ExecuteCommand()
54     {
55         if (command)
56         {
57             command->Execute();
58         }
59     }
60 
61 private:
62     Command *command = nullptr;
63 };
64 
65 int main()
66 {
67     ConcreteCommand command(new Receiver());
68 
69     Invoker invoker;
70     invoker.SetCommand(&command);
71     invoker.ExecuteCommand();
72 
73     return 0;
74 }
Command Pattern

下面结合代码进行分析:

  • 依赖:这里的 main 函数相当于 Client , main 函数中仅仅创建并使用了 Receiver 、 ConcreteCommand 、 Invoker 的实例,相当于将它们的实例作为局部变量,因此 main 函数( Client )依赖了这三个类。​
  • 关联: ​Invoker 类中有指向 Command 或其子类的指针,该指针通过成员函数 void SetCommand(Command *c) 被赋值,因此 Invoker 关联了 Command 。
  • 聚合: ConcreteCommand 类中有指向 Receiver 的指针,该指针通过构造函数 ConcreteCommand(Receiver *r) 在 ConcreteCommand 实例被创建的同时被初始化,因此,ConcreteCommand聚合了Receiver。​

需要特别说明的是,受GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》误导,几乎所有资料的UML类图都将 Ivoker 和 Command 画成了聚合关系,而将 ConcreteCommand 和 Receiver 画成了关联关系(正好跟上图相反),这显然是不对的。理由如下:​

  • “绑定”关系的牢固性不同:
    •  Invoker 和 Command 通过 void Invoker::SetCommand(Command *c) 建立关系,并且,通过多次调用函数,可以对 Invoker::command 这个成员变量赋不同的值,即 Invoker 实例可以“绑定”不同的 Command 实例,这种“绑定”是不稳定的,可更换的。​
    • 反观 ConcreteCommand ,它通过自身的构造函数 ConcreteCommand(Receiver *r) 与 Receiver 实例“绑定”,并且 ConcreteCommand 实例一旦构造完成,它所“绑定”的 Receiver 实例就不能换成其它 Receiver 实例了。显然, ConcreteCommand 和 Receiver 之间的“绑定”关系,要比 Invoker 和 Command 之间的“绑定”关系牢固得多。​
  • 关系层次不同:
    • 普通成员函数 void Invoker::SetCommand(Command *c) 暗示 Invoker 和 Command 之间是同一个层次上的“平等”关系​。
    • 构造函数 ConcreteCommand(Receiver *r) 暗示 ConcreteCommand 和 Receiver 之间是整体与部分的关系。​

参考

 
 
 
 
 
 
 
 
 
 
 
 
 

posted @ 2022-04-06 15:01  同勉共进  阅读(602)  评论(0编辑  收藏  举报