6_1、C++类:UML

一、UML简介

我们在进行软件开发的时候,如果只靠脑子想,只有一个看不见的软件规划、软件架构,可能写程序时会影响你的思路的清晰,或者中间间断了以后会忘记当初的规划而要重新回忆或重新规划。如果我们可以把程序设计用图形表达出来,就会让我们的思路很清晰,也很容易进行合理的优化,我们和其他的软件开发人员或者用户就能够进行更好的沟通。
面向对象设计图应运而生,它能清楚的描述以下几个问题:

  1. 类,包括数据成员和函数成员。
  2. 对象,类的实例。
  3. 类及对象的关系,继承或者包含。
  4. 类及对象之间的联系,相互作用与消息传递等。

现在国际上标准的面向对象标记方法称为UML,即统一建模语言。这种标记分为两类图形符号:

  1. 表示符号和连接符号。表示符号用来表示类和对象。
  2. 连接符号用来表示类和对象之间的关系和联系。

二、UML图种类

UML中有9种图:类图、对象图、用例图、顺序图、协作图、状态图、活动图、组件图和实施图。这里只介绍下类图和对象图。

  • 类图:
    类图
  • 对象图:
    对象图

ML中类及对象的关系有以下几种:依赖、关联、聚合、组合、泛化和实现。

  • 依赖关系
    如果类A使用了类B,或者说如果类B的变化会影响类A,则说类A依赖于类B。一般有以下几种情况属于依赖关系:类A调用类B的成员函数;类B的对象是类A的成员变量;类A的成员函数使用了类B类型的参数。依赖关系用带箭头的虚线表示。
    依赖关系

  • 关联关系
    关联关系
    重数A表示类B的每个对象与类A的多少个对象相关联,重数B则表示类A的每个对象与类B的多少个对象发生作用。比如,老师和学生的关联,老师类的重数应该是1,学生类的重数可能是n。

  • 聚合关系
    聚合关系
    聚合表示类之间的关系是整体和部分的关系,但是聚合关系中的整体和部分是可以分开的。比如,我们可以选择某个主板、硬盘、机箱等配件组装一台电脑,以后这台电脑可以随时更换配件,还一样能够组成一台电脑。聚合关系用带空心菱形的实线表示。

  • 组合关系
    在这里插入图片描述
    组合关系也是整体和部分的关系,但是它与聚合关系不同的是,整体和部分是不可以分开的。比如,我们的房子由客厅、卧室、厨房等组合而成,房子不能和别人的房子对换客厅等任何房间,客厅、卧室等不能和房子分开。组合关系用带实心菱形的实线表示。

  • 泛化关系
    如果类A和类B是从类C继承的子类,那么类C就是类A和类B的泛化。泛化关系用带空心三角形的实线表示:
    在这里插入图片描述

  • 实现关系
    在这里插入图片描述
    实现关系用来说明接口和实现接口的类之间的关系。实现关系图用带空心三角形的虚线表示。

三、UML类图

3.1 绘制 UML 类图

  1. 第 1 步:识别类 (Identify the Classes)
    首先,你需要从需求或问题描述中找出核心的名词,这些通常是你的类。

    • 示例:在“一个用户可以创建多个订单”这个需求中,用户订单 就是两个核心类。
  2. 第 2 步:定义类的三大要素
    每个类在类图中都由一个分为三栏的矩形表示:

    1. 类名 (Class Name):位于顶部,居中。首字母大写。
      • 示例User
    2. 属性 (Attributes):位于中间,列出类的成员变量。
      • 格式[可见性] 属性名 : 类型 [ = 默认值]
      • 示例- id : int, + name : string
    3. 方法 (Operations/Functions):位于底部,列出类的成员函数。
      • 格式[可见性] 方法名(参数列表) : 返回类型
      • 示例+ getName() : string, - calculateTotal() : double

可见性符号

  • + Public (公共)
  • - Private (私有)
  • # Protected (受保护)

一个完整的类图元素示例:

+-------------------+
|      User         |
+-------------------+
| - id : int        |
| - name : string   |
| - email : string  |
+-------------------+
| + User(name, email) |
| + getName() : string |
| + setEmail(newEmail : string) : void |
+-------------------+
  1. 第 3 步:建立类之间的关系 (Establish Relationships)
    这是类图的核心。类与类之间不是孤立的,它们通过各种关系协同工作。

    1. 关联 (Association)

      • 含义:表示两个类之间存在某种静态的、长期的联系。
      • 符号:一条实线。
      • 示例UserOrder 之间是关联关系。
      • 可以添加多重性 (Multiplicity):表示一个类的实例可以与另一个类的多少个实例相关联。
        • 1:一个
        • *:零个或多个
        • 0..1:零个或一个
        • 2..5:两个到五个
      • 关联示例:一个 User 可以有多个 Order,而一个 Order 只属于一个 User
      User "1" -- "*" Order
      

      (这读作:一个 User 关联零个或多个 Order)

    2. 聚合 (Aggregation)

      • 含义:表示“整体-部分”关系,其中部分可以脱离整体而独立存在。是一种较弱的拥有关系。
      • 符号:一条带空心菱形的实线,菱形指向“整体”。
      • 示例CarWheel。车包含轮子,但轮子即使从车上卸下也仍然是一个轮子。
      Car <>- Wheel
      
    3. 组合 (Composition)

      • 含义:也表示“整体-部分”关系,但部分不能脱离整体而存在。是一种强的拥有关系,整体负责部分的生命周期。
      • 符号:一条带实心菱形的实线,菱形指向“整体”。
      • 示例PersonHeart。心脏是人的一部分,如果人不存在了,心脏也就失去了其作为“人心脏”的意义。
      Person <*>-- Heart
      
    4. 继承 (Inheritance / Generalization)

      • 含义:表示“is-a”关系,子类继承父类的属性和方法。
      • 符号:一条带空心三角形的实线,三角形指向父类
      • 示例StudentTeacher 都是 Person
      Student --|> Person
      Teacher --|> Person
      
    5. 实现 (Realization)

      • 含义:表示一个类实现了一个接口(或多个接口)。
      • 符号:一条带空心三角形虚线,三角形指向接口
      • 示例Car 类实现了 Driveable 接口。
      Car --|.. Driveable
      
    6. 依赖 (Dependency)

      • 含义:表示一个类在某个特定时刻临时使用到了另一个类。这是一种最弱的关系。
      • 符号:一条带箭头的虚线,箭头指向被依赖的类。
      • 示例OrderProcessor 在处理订单时,需要调用 EmailService 来发送邮件。OrderProcessor 依赖于 EmailService
      OrderProcessor ..> EmailService
      
  2. 使用工具绘制
    手动绘制容易出错且难以维护。建议使用专业的 UML 工具:

    • 入门级:Draw.io (免费在线)、Lucidchart
    • 专业级:StarUML、Visual Paradigm
    • IDE 集成:IntelliJ IDEA Ultimate、Visual Studio (需安装插件)

3.2 绘制类图需要注意的关键点

1. 明确建模目的,避免过度设计

  • 问自己:“这张图是给谁看的?想表达什么核心概念?”
  • 不要试图将代码的每一个细节都画出来。类图是设计蓝图,不是代码的逐行翻译。
  • 保持简洁:只画出关键的类、属性、方法和关系。过多的细节会让图变得混乱,失去沟通价值。

2. 正确使用关系,避免滥用

  • 关联 vs 依赖:如果两个类总是一起出现,有长期联系,用关联。如果只是某个方法临时调用一下,用依赖
  • 聚合 vs 组合:这是最容易混淆的。问自己一个问题:“如果‘整体’被销毁,‘部分’还能独立存在吗?”
    • 聚合 (Aggregation)
    • 不能组合 (Composition)
  • 优先使用关联,而非继承:不要为了复用少量代码而建立继承关系。过度使用继承会导致类层次结构僵化。优先考虑组合或依赖。

3. 命名规范要统一

  • 类名:使用名词,首字母大写,采用驼峰命名法 (CamelCase),如 OrderItem
  • 属性和方法名:使用有意义的名称,首字母小写,采用驼峰命名法,如 orderDate, calculateTotal()
  • 一致性:在整个项目中保持相同的命名风格。

4. 善用多重性 (Multiplicity)

  • 多重性是类图中传递设计信息的关键。不要忘记添加它。
  • 例如,UserAddress 的关系可能是 10..* (一个用户可以有零个或多个地址)。

5. 控制图的复杂度

  • 拆分大图:如果系统非常复杂,可以使用包图 (Package Diagram) 将类组织成不同的模块(如 UserModule, OrderModule),然后为每个包单独绘制详细的类图。
  • 使用接口:接口可以简化类图。通过定义接口,可以隐藏具体实现类的复杂性,只展示系统的交互契约。

6. 保持图与代码的同步

  • UML 图是“活的文档”。代码发生重大变更后,必须及时更新相应的类图。
  • 否则,这些图会很快过时,失去其作为设计文档和沟通工具的价值。最好使用支持“代码反向生成”的工具。

绘制 UML 类图是一个从抽象到具体的过程。关键在于识别核心概念(类)、定义它们的特征(属性和方法)、并建立它们之间的正确关系。同时,要时刻记住为沟通服务,保持图的清晰、简洁和准确

3.3 接口

在 UML 类图中,接口(Interface) 是一种抽象的“契约”或“行为规范”,它仅定义了类或组件应该提供的方法(行为)列表,但不包含方法的具体实现,也不存储数据(即无属性)。其核心作用是解耦“行为定义”与“行为实现”,让不同的类可以遵循统一的规范提供功能,同时不依赖彼此的具体实现。

  1. 纯抽象性:仅包含方法签名(方法名、参数、返回值),无任何实现代码;
  2. 无状态:不定义属性(不能存储数据),仅描述“能做什么”,不关心“数据如何存储”;
  3. 多实现性:一个类可以同时实现多个接口(弥补了很多语言中“单继承”的限制);
  4. 契约性:只要类实现了接口,就必须重写接口中的所有方法,保证行为符合规范。

1. UML类图中接口的两种表示方式

在类图中,接口有两种标准画法,根据场景选择使用:

  1. 「棒棒糖」表示法(Lollipop Notation)

    • 形状:用一个小圆圈表示接口,圆圈下方标注接口名;
    • 关系:实现接口的类用带空心三角的虚线连接到接口(三角指向接口),虚线可理解为“遵循契约”。
  2. 「类式」表示法(Class-like Notation)

    • 形状:用一个矩形表示接口,矩形顶部标注 <<interface>>( stereotypes,构造型),下方依次列出接口的方法(无属性);
    • 关系:与“棒棒糖”表示法一致,实现类用带空心三角的虚线连接到接口。

2. 接口的典型示例

以“电子设备的远程控制”场景为例:所有可被远程控制的设备(如电视、空调、投影仪),都需要提供“开机”“关机”“调节音量”的功能——这组功能可以抽象为 RemoteControllable 接口,不同设备则实现该接口并提供具体逻辑。

  1. 用「棒棒糖」表示法绘制的类图
+----------------+       <<interface>>
|     TV         |       RemoteControllable
+----------------+            O
| - channel: int |            |
+----------------+            | (虚线,带空心三角)
| + turnOn(): void|           |
| + turnOff(): void|----------+
| + adjustVolume(level: int): void |
| + changeChannel(ch: int): void |
+----------------+

+----------------+
|    AirConditioner |
+----------------+
| - temperature: int |
+----------------+
| + turnOn(): void|----------+
| + turnOff(): void|---------+
| + adjustVolume(level: int): void |
| + setTemperature(temp: int): void |
+----------------+
  1. 用「类式」表示法绘制的类图
+----------------+       +--------------------------------+
|     TV         |       | <<interface>> RemoteControllable |
+----------------+       +--------------------------------+
| - channel: int |       | + turnOn(): void                |
+----------------+       | + turnOff(): void               |
| + turnOn(): void|       | + adjustVolume(level: int): void|
| + turnOff(): void|-------(虚线,带空心三角)-------------+
| + adjustVolume(level: int): void |
| + changeChannel(ch: int): void |
+----------------+

+----------------+
|    AirConditioner |
+----------------+
| - temperature: int |
+----------------+
| + turnOn(): void|-------(虚线,带空心三角)-------------+
| + turnOff(): void|------------------------------------+
| + adjustVolume(level: int): void |
| + setTemperature(temp: int): void |
+----------------+

3. 对应的代码实现

  • (以Java为例)
    接口定义(契约):
// 接口:定义远程可控设备的统一行为
public interface RemoteControllable {
   
   
    void turnOn();       // 开机(无实现)
    void turnOff();      // 关机(无实现)
    void adjustVolume(int level); // 调节音量(无实现)
}

TV类实现接口(遵循契约并提供具体逻辑):

public class TV implements RemoteControllable {
   
   
    private int channel; // TV的私有属性(接口中无此定义)

    // 必须实现接口的所有方法
    @Override
    public void turnOn() {
   
   
        System.out.println("电视开机,当前频道:" + channel);
    }

    @Override
    public void turnOff() {
   
   
        System.out.println("电视关机");
    }

    @Override
    public void adjustVolume(int level) {
   
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值