文章目录
- 1.UML
- 2.Class Diagram
- 2.1 类图的表示
- 2.2 类间的关系
- 2.2.1 关联
- 2.2.2 聚合
- 2.2.3 组合
- 2.2.4 泛化(继承)
- 2.2.5 实现(接口实现)
- 2.2.6 依赖
- 2.3 类图的作用
参考:Class Diagram | Unified Modeling Language (UML)
UML是软件设计和面向对象编程中常用的一种标准化建模语言,对于可视化系统结构和功能非常有用,类图有多种不同类型的图:用例图,类图,时序图,活动图和状态图,每类图都有规范的表示,不局限于语言,本文介绍以下内容
(1)UML的图分类
(2)类图的表示
(3)类图的关系
(4)类图的作用
1.UML
Unified Modeling Language(UML)是一种标准化的建模语言,用于在软件系统开发过程中可视化、描述和设计软件的结构与行为。UML 提供了一系列图表,帮助开发人员、系统架构师以及相关人员交流系统的设计和功能。UML 包括许多不同类型的图:
- 用例图(Use Case Diagram):描述系统的功能需求以及外部参与者的交互
- 类图(Class Diagram):描述系统的静态结构,展示类、属性、方法以及类之间的关系
- 时序图(Sequence Diagram):展示对象之间的消息传递顺序,描述时间序列中的动态行为
- 活动图(Activity Diagram):用于表示系统中的流程和业务逻辑
- 状态图(State Diagram):描述系统或对象的状态以及状态之间的转换
2.Class Diagram
2.1 类图的表示
在类图中,类用方框表示,每个方框包含三个部分:类名,属性和方法,方框之间的连接线说明这些类的关系。类图提供了系统设计的高级概述,有助于沟通和记录软件的结构。它们是面向对象设计的基本工具,在软件开发生命周期中起着至关重要的作用。
如图所示的方框为类Car
的类图表示
- Class Name:第一个小框描述了类名,通常放在方框中间加粗,类民首字母大写
- Attributes:第二个小框介绍了类的属性(字段),包括属性名,属性的可见性和数据类型
- Methods:第三个小框介绍了类的方法,代表了类的行为函数,包括可见性,返回类型和参数
- Visibility:可见性表示了属性和方法的可获取水平
(1)+:public公共,对所有类可见
(2)-:private私有,仅在类内可见
(3)#:protected保护,对子类可见
(4)~:包或默认可见性,对同一包中的类可见 - Parameter:方法中的参数显示了类之间的信息流动
(1)in:输入参数是从调用对象(client)到被调用对象(server)的参数
(2)out:输出参数是从被调用对象(server)到调用对象(client)的参数
(2)inout:输入输出参数同时作为输入和输出,在client和server之间进行信息流通
2.2 类间的关系
在类图中,类之间的关系有以下几种:
比较常见的分类是6种:关联,聚合,组合,泛化(继承),实现,依赖,还有一些概念更细分的,比如直接关联,使用依赖等。
关系 | 描述 |
---|---|
关联Association | 表示类之间的长期持久关系,一个类持有另一个类的引用,通常通过成员变量表示 |
聚合Aggregation | 整体和部分关系,部分可以独立于整体 |
组合Composition | 整体和部分关系,部分不能独立于整体 |
泛化Generalization | 子类继承父类属性和方法,可扩展和重写 |
实现Realization | 通过抽象类定义接口,客户端和实现解耦 |
依赖Dependency | 表示类之间临时的关系,一个类需要短暂使用另一个类的某个功能,通常通过方法调用实现 |
2.2.1 关联
此处介绍两种:关联和直接关联,由于概念比较类似放在一个小节
(1)关联Association:是泛指两个类之间的关系,可以是双向或单向,也可以是弱耦合的逻辑联系,用直线表示
关联举例:选课系统中学生和课程之间是双向关联,学生可以注册多门课程,每门课程可以有多个学生,Student类和 Course类存在关联关系,但是并不代表Student类直接引用 Course类的实例。如下面代码所示,student1和course1的实例创建是独立的,但是student1中的enroll方法会用到course1,course1的add_student也会添加student1
class Student:
def __init__(self, name):
self.name = name
self.courses = [] # 一个学生可以注册多门课程
def enroll(self, course): # 学生注册课程,课程添加学生
self.courses.append(course)
course.add_student(self)
class Course:
def __init__(self, title):
self.title = title
self.students = [] # 一门课程可以有多个学生
def add_student(self, student):
self.students.append(student)
# 创建实例
student1 = Student("Alice")
course1 = Course("Math")
# 学生注册课程
student1.enroll(course1)
(2)直接关联Direct Association:强调单向依赖关系,一个类直接持有或依赖另一个类的实例,用带单向箭头的直线表示,箭头指向被持有的类。
直接关联是关联的一种更具体的表现,表示类之间有更明确的耦合关系
直接关联举例:学生和身份证之间存在一种直接关联。每个学生都必须有一个身份证,学生对象直接持有身份证对象。Student类和IDCard类之间有一个单向的直接关联,这是一种更强的依赖关系,表示Student 类的对象必须持有一个 IDCard 类的对象才能正常工作。如下面代码所示,student的实例创建必须用到id_card实例
class IDCard:
def __init__(self, card_number):
self.card_number = card_number
class Student:
def __init__(self, name, id_card):
self.name = name
self.id_card = id_card # 学生直接持有身份证的引用
# 创建身份证实例
id_card = IDCard("123456789")
student = Student("Alice", id_card)
# 访问学生的身份证信息
print(student.id_card.card_number) # 输出: 123456789
2.2.2 聚合
- 聚合Aggregation:一种特殊的关联形式,表示整体—部分(whole-part)关系,它表示一种更强的关系,其中一个类(整体)包含或由另一个类(部分)组成,在这种关系中,子类可以独立于其父类而存在。用带空心菱形箭头的直线表示,箭头指向整体
聚合举例:公司拥有员工,但是员工可以在没有公司的情况下存在。换句话说,公司雇佣了员工,但员工可以辞职并加入另一家公司,因此公司和员工之间的关系是聚合。Company 是整体,Employee 是部分,如下面代码所示,company实例由employee1和employee2实例组成,但是删除company并不会影响employee1和employee2
class Employee:
def __init__(self, name):
self.name = name
def work(self):
print(f"{self.name} is working.")
class Company:
def __init__(self, name):
self.name = name
self.employees = [] # 聚合关系,公司由多个员工组成
def hire(self, employee):
self.employees.append(employee)
print(f"{employee.name} has been hired by {self.name}.")
def list_employees(self):
print(f"{self.name} has the following employees:")
for emp in self.employees:
print(f"- {emp.name}")
# 创建公司和员工实例
employee1 = Employee("Alice")
employee2 = Employee("Bob")
company = Company("TechCorp")
# 公司雇佣员工
company.hire(employee1)
company.hire(employee2)
# 公司列出员工
company.list_employees()
# 即使公司不存在,员工仍然可以存在
del company
employee1.work() # 输出: Alice is working.
2.2.3 组合
- 组合Composition:一种更强的聚合形式,表示更重要的所有权或依赖关系。在组合中,部分类不能独立于整体类而存在。组合带实心菱形箭头的直线表示,箭头指向整体
组合举例:手机里通讯录和联系人的关系是组合关系,如果通讯录被删除,那么里面的联系人也会删除,也就是说联系人不能独立于通讯录存在。ContactBook 是整体,Contact 是部分
class Contact:
def __init__(self, name, phone):
self.name = name
self.phone = phone
def display_contact(self):
print(f"Name: {self.name}, Phone: {self.phone}")
class ContactBook:
def __init__(self):
self.contacts = [] # 组合关系,通讯录拥有多个联系人
def add_contact(self, name, phone):
contact = Contact(name, phone) # 创建联系人的同时,直接把它归属到通讯录
self.contacts.append(contact)
# 创建通讯录实例
contact_book = ContactBook()
# 向通讯录添加联系人
contact_book.add_contact("Alice", "123-456-7890")
contact_book.add_contact("Bob", "987-654-3210")
# 删除通讯录
del contact_book
# 联系人对象也会随之销毁,无法独立存在
2.2.4 泛化(继承)
- 泛化Generalization(继承Inheritance):表示类之间的“is-a”关系,其中一个类(subclass或child)继承另一个类(supclass或parent)的属性和行为,继承使得子类能够复用父类的代码,同时可以扩展或重写父类的功能。用一条带空心三角箭头的实线表示,箭头指向父类
继承举例:一个银行账户下的储蓄账户和信用账户可以共用这个银行账户的信息,BankAccount是父类,SavingsAccount 是子类,CreditAccount 是子类
# 父类:BankAccount
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number # 账户号码
self.balance = balance # 账户余额
def deposit(self, amount):
"""存款方法"""
self.balance += amount
print(f"Deposited {amount}. New balance: {self.balance}")
def withdraw(self, amount):
"""取款方法"""
if amount > self.balance:
print("Insufficient funds!")
else:
self.balance -= amount
print(f"Withdrew {amount}. New balance: {self.balance}")
# 子类:SavingsAccount
class SavingsAccount(BankAccount):
def __init__(self, account_number, balance, interest_rate):
super().__init__(account_number, balance)
self.interest_rate = interest_rate # 储蓄账户的利率
def add_interest(self):
"""添加利息"""
interest = self.balance * self.interest_rate
self.balance += interest
print(f"Interest added: {interest}. New balance: {self.balance}")
# 子类:CreditAccount
class CreditAccount(BankAccount):
def __init__(self, account_number, balance, credit_limit):
super().__init__(account_number, balance)
self.credit_limit = credit_limit # 信用账户的信用额度
def withdraw(self, amount):
"""重写父类的取款方法,允许透支"""
if amount > self.balance + self.credit_limit:
print("Exceeds credit limit!")
else:
self.balance -= amount
print(f"Withdrew {amount}. New balance: {self.balance}")
# 测试代码
savings = SavingsAccount("12345", 1000, 0.05)
savings.deposit(500)
savings.add_interest()
credit = CreditAccount("67890", 500, 1000)
credit.withdraw(200)
credit.withdraw(1500) # 允许透支
credit.withdraw(2000) # 超过信用额度
2.2.5 实现(接口实现)
- 实现Realization(接口实现Interface Implementation):是在面向对象设计中,通过接口或抽象类来定义某些功能,来描述某一组行为的约定,而具体的实现由不同的类来完成。可以避免直接依赖具体类,而是依赖接口或抽象类,从而实现代码的解耦。用带空心三角箭头的虚线表示,箭头指向接口
实现举例:有一个支付系统,它支持多种支付方式,如信用卡支付,PayPal 支付和比特币支付,每种支付方式都有自己的具体实现方式,但它们都应该符合统一的支付行为,比如 pay()。比如下面代码中,IPayment是一个抽象类,也就是客户端接口,声明了一个pay()方法,但是没有具体实现,而CreditCardPayment,PayPalPayment和BitcoinPayment都会各自具体实现pay(),对于客户端代码process_payment传入任何一个payment_method,都可以使用pay(),客户端代码不关心具体是哪种支付方式,只需要知道它实现了IPayment 接口即可。
from abc import ABC, abstractmethod
# 定义一个支付接口(抽象基类)
class IPayment(ABC):
@abstractmethod
def pay(self, amount):
"""支付方法,所有支付方式都必须实现"""
pass
# 具体实现:信用卡支付
class CreditCardPayment(IPayment):
def pay(self, amount):
print(f"Paid {amount} using Credit Card.")
# 具体实现:PayPal 支付
class PayPalPayment(IPayment):
def pay(self, amount):
print(f"Paid {amount} using PayPal.")
# 具体实现:比特币支付
class BitcoinPayment(IPayment):
def pay(self, amount):
print(f"Paid {amount} using Bitcoin.")
# 客户端代码,使用支付系统
def process_payment(payment_method, amount):
payment_method.pay(amount)
# 测试代码
credit_card = CreditCardPayment()
paypal = PayPalPayment()
bitcoin = BitcoinPayment()
# 使用不同的支付方式处理支付
process_payment(credit_card, 100)
process_payment(paypal, 200)
process_payment(bitcoin, 300)
这种实现关系可以很容易地为系统添加新的实现方式(如支持新的支付方式),而不需要修改现有代码,新增的实现类只需要实现接口即可,完全可以独立于其他代码进行开发,将客户端代码与具体实现解耦来提高系统的可维护性,如果不使用实现关系,直接让 process_payment 依赖于具体的支付类,那么就会出现下面这种不停维护客户端代码的情况
def process_payment(payment_method, amount):
if isinstance(payment_method, CreditCardPayment):
payment_method.pay(amount)
elif isinstance(payment_method, PayPalPayment):
payment_method.pay(amount)
elif isinstance(payment_method, BitcoinPayment):
payment_method.pay(amount)
# 如果以后新增一种支付方式,还要在这里加新的条件分支
2.2.6 依赖
- 依赖Dependency:表示一个元素对另一个元素的使用或依赖,但这种关系不像关联或继承那么强,它表示类之间更松散的耦合连接,典型场景是类的方法依赖于另一个类的对象作为参数或返回。 用带普通箭头的虚线表示,箭头指向被依赖者
依赖举例:办公室工作人员会使用打印机打印东西,OfficeWorker 依赖 Printer,但并没有保存 Printer 的实例,它只在需要打印时使用Printer
class Printer:
def print_document(self, document):
print(f"Printing: {document}")
class OfficeWorker:
def __init__(self, name):
self.name = name
def do_work(self, document, printer):
printer.print_document(document)
使用依赖Usage Denpency是依赖的一种特定形式,表示一个类(client)利用或依赖另一个类(server)来执行某些任务或访问某些功能。client类依赖于server类提供的服务,但不拥有或创建其实例,上面的打印机例子也是一种使用依赖,此处不再细分描述
2.3 类图的作用
- 建模类结构:类图通过表示类及其属性、方法和关系,帮助对系统结构进行建模,提供了系统架构的清晰而有条理的视图
- 理解关系:类图描绘了类之间的关系,例如关联、聚合、组合、继承和依赖关系,有助于开发设计等人员了解系统结构
- 实施蓝图:类图是软件实施的蓝图,通过说明类、它们的属性、方法及其之间的关系来指导开发人员编写代码
- 代码生成:一些软件开发工具和框架支持从类图生成代码,开发人员可以从可视化表示中生成大量代码,从而减少手动错误的可能性并节省开发时间
- 识别抽象和封装:类图能识别抽象以及在类中封装数据和行为,支持面向对象设计的原则,例如模块化和信息隐藏