第29课:Python|面向对象高级【组合、聚合、类与类之间关系深度解析】

在这里插入图片描述


📖 开篇导读

在之前的课程中,我们系统学习了面向对象的三大特性:封装、继承、多态。继承让我们能够复用父类的代码,建立起“is-a”的关系。但是,继承并非解决代码复用的唯一方法,有时候过度使用继承会让类层次变得臃肿、脆弱。

在面向对象设计中,类与类之间的关系除了继承(泛化),还有依赖关联聚合组合。理解这些关系,可以帮助你设计出更灵活、更低耦合的系统。特别是组合(Composition)和聚合(Aggregation),它们体现了“has-a”的关系,是现代软件设计中提倡的**“组合优于继承”**原则的核心。

💡 工作场景

  • 组合:订单包含多个商品,商品的生命周期依赖于订单。订单删除,商品项自然消失。
  • 聚合:学院包含多个学生,学生离开学院后仍然存在。学院解散,学生还可以转到其他学院。
  • 依赖:汽车需要汽油,但汽车不拥有汽油,汽油是从加油站买的。

本课将通过理论讲解和大量代码案例,深入剖析类之间的六种关系,重点讲解组合聚合的实现与区别,并解释为什么推荐优先使用组合而不是继承。学完本课,你将能够从更高维度设计类的结构,写出高内聚、低耦合的代码。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣理解类之间六种基本关系(泛化、实现、依赖、关联、聚合、组合)面试考点,UML类图基础
2️⃣掌握依赖关联聚合组合的区别及代码实现写出低耦合代码
3️⃣理解组合优于继承原则,并能在实际中运用改善代码设计
4️⃣掌握Python中实现组合和聚合的典型模式构建灵活的结构
5️⃣能够识别和重构过度继承的设计提升代码可维护性

🔥 面试考点:“组合和聚合的区别是什么?”“为什么要说组合优于继承?”“请举例说明依赖、关联和组合的关系。”


📚 知识点理论精讲

一、类与类之间的六种关系

根据UML(统一建模语言),类之间的关系按耦合度由低到高可以分为:

关系类型英文描述UML符号代码体现
依赖Dependency一个类使用另一个类作为参数或局部变量,但不持有引用虚线箭头方法参数、局部变量
关联Association一个类持有另一个类的引用,双方相对独立,没有生命周期依赖实线箭头实例变量(引用)
聚合Aggregation关联的一种特殊形式,表示“整体-部分”关系,部分可以脱离整体独立存在空心菱形箭头实例变量,但整体不负责部分的生命周期
组合Composition更强的聚合,部分的生命周期由整体负责,同生共死实心菱形箭头实例变量,在整体构造时创建,析构时销毁
泛化Generalization继承关系,子类继承父类,表示“is-a”空心三角箭头class Child(Parent):
实现Realization接口与实现类的关系虚线空心三角箭头抽象基类与子类

🧠 通俗类比

  • 依赖:你需要在某个方法中使用一把螺丝刀,你去借或者临时买一个,用完就还了(不持有)。
  • 关联:你有一个同事,你们都知道彼此,但各自独立生活。
  • 聚合:你有一台电脑,电脑里有硬盘。如果电脑坏了,硬盘可以取出装到别的电脑上使用(整体与部分可分离)。
  • 组合:你有一部手机,手机里有电池。如果手机报废,电池通常也跟着报废(部分随整体消亡)。

二、依赖(Dependency)

依赖是最弱的关系,一个类在方法中使用了另一个类,但类本身不持有对方的引用。通常通过方法参数、返回值或局部变量体现。

class Printer:
    def print(self, document):
        print(f"打印: {document}")

class Computer:
    def print_document(self, printer: Printer, document):
        # 依赖:通过参数传入,不持有
        printer.print(document)

三、关联(Association)

关联表示一个类知道另一个类,持有对方的引用,但双方生命周期独立,没有强制的整体-部分约束。关联可以是单向的也可以是双向的。

class Teacher:
    def __init__(self, name):
        self.name = name

class Student:
    def __init__(self, name, teacher=None):
        self.name = name
        self.teacher = teacher   # 关联 Teacher

四、聚合(Aggregation)

聚合表示“整体-部分”的关系,但部分可以脱离整体独立存在。整体不负责部分的生命周期。在代码层面,通常通过构造函数或setter传递外部创建的对象,整体销毁时部分仍然存活。

class Player:
    def __init__(self, name):
        self.name = name

class Team:
    def __init__(self, name):
        self.name = name
        self.players = []   # 聚合:队伍包含球员
    
    def add_player(self, player: Player):
        self.players.append(player)
    
    def remove_player(self, player: Player):
        self.players.remove(player)

# 球员可以脱离队伍存在
p1 = Player("张三")
team = Team("战狼队")
team.add_player(p1)   # 聚合关系
del team   # 队伍销毁,p1仍然存在

五、组合(Composition)

组合是更强的聚合,部分的生命周期完全由整体控制。整体创建时部分也被创建,整体销毁时部分也随之销毁。在代码中,通常整体在__init__中创建部分,不使用外部传入的对象。

class Engine:
    def __init__(self, power):
        self.power = power
    def start(self):
        print(f"引擎({self.power}马力)启动")

class Car:
    def __init__(self, model, engine_power):
        self.model = model
        self.engine = Engine(engine_power)   # 组合:汽车创建时引擎随之创建
    
    def start(self):
        print(f"{self.model} 启动")
        self.engine.start()

car = Car("Tesla", 300)
car.start()
del car   # 汽车销毁,引擎也随之销毁

💡 工作应用:GUI编程中,窗口(Window)包含按钮(Button)通常使用组合;电商系统中,订单(Order)包含订单项(OrderItem)通常使用组合;公司包含部门,部门可以独立存在,则使用聚合。


六、组合优于继承原则

《设计模式》一书中提出:优先使用对象组合,而不是类继承

  • 继承的缺点:父类变化会影响所有子类;子类不得不继承父类的所有方法,可能导致不必要的方法暴露;继承复用是“白箱复用”,内部细节暴露。
  • 组合的优点:灵活,可以在运行时改变行为;只依赖接口,不依赖具体实现;封装性好,是“黑箱复用”。

6.1 继承的典型案例问题

class Bird:
    def fly(self):
        print("飞行")

class Penguin(Bird):
    def fly(self):
        raise Exception("企鹅不会飞")

这里企鹅继承鸟,但飞行行为不合适。如果使用组合,可以将“行为”抽取出来,动态赋予。

6.2 组合改进

class Flyable:
    def fly(self):
        print("飞行")

class NonFlyable:
    def fly(self):
        print("不会飞")

class Bird:
    def __init__(self, flight_behavior):
        self.flight_behavior = flight_behavior
    def fly(self):
        self.flight_behavior.fly()

penguin = Bird(NonFlyable())
eagle = Bird(Flyable())

💻 代码案例实操

案例1:依赖、关联、聚合、组合对比

"""
relationship_demo.py
演示四种关系的代码体现
"""

# 1. 依赖:使用方法参数或局部变量
class Logger:
    def log(self, msg):
        print(f"[LOG] {msg}")

class Service:
    def process(self, logger: Logger, data):
        # 依赖:logger作为参数传入,不存储
        logger.log(f"处理数据: {data}")
        result = f"结果:{data}"
        logger.log(f"完成:{result}")
        return result

# 2. 关联:持有一个引用,但不负责生命周期
class Address:
    def __init__(self, city, street):
        self.city = city
        self.street = street

class Person:
    def __init__(self, name, address=None):
        self.name = name
        self.address = address   # 关联,可以后设置
    
    def set_address(self, addr):
        self.address = addr

# 3. 聚合:整体包含部分,部分可独立存在
class CPU:
    def __init__(self, model):
        self.model = model

class Computer:
    def __init__(self, brand):
        self.brand = brand
        self.cpu = None   # 聚合,外部传入或setter
    
    def install_cpu(self, cpu: CPU):
        self.cpu = cpu

cpu = CPU("Intel i7")
pc = Computer("Dell")
pc.install_cpu(cpu)
# cpu 可以独立于 pc 存在

# 4. 组合:整体创建并管理部分生命周期
class Wheel:
    def __init__(self, size):
        self.size = size

class Car:
    def __init__(self, brand):
        self.brand = brand
        # 在构造函数内部直接创建轮子,外部无法独立获取轮子
        self.wheels = [Wheel(17) for _ in range(4)]
    
    def rotate_wheels(self):
        for w in self.wheels:
            print(f"{self.brand} 车轮旋转")

car = Car("Toyota")
car.rotate_wheels()
# 轮子没有暴露到外部,生命周期绑定在车上

案例2:订单-商品组合关系

"""
order_composition.py
演示组合:订单包含订单项,订单销毁时订单项也随之销毁
"""

class OrderItem:
    def __init__(self, product_name, price, quantity):
        self.product_name = product_name
        self.price = price
        self.quantity = quantity
    
    def subtotal(self):
        return self.price * self.quantity
    
    def __repr__(self):
        return f"Item({self.product_name}, {self.quantity}个)"

class Order:
    def __init__(self, order_id):
        self.order_id = order_id
        self.items = []   # 组合:在订单内部创建和管理
    
    def add_item(self, product_name, price, quantity=1):
        item = OrderItem(product_name, price, quantity)
        self.items.append(item)
        print(f"添加商品: {product_name} x{quantity}")
    
    def total(self):
        return sum(item.subtotal() for item in self.items)
    
    def display(self):
        print(f"订单号: {self.order_id}")
        for item in self.items:
            print(f"  {item}: 小计 {item.subtotal()}元")
        print(f"总计: {self.total()}元")
    
    # 析构时演示(实际不用依赖__del__)
    def __del__(self):
        print(f"订单 {self.order_id} 销毁,订单项也随之销毁")

# 使用
order = Order("ORD-001")
order.add_item("《Python编程》", 89, 2)
order.add_item("鼠标", 45, 1)
order.display()

# 当order被删除,order.items中的OrderItem对象也不再被引用
del order

案例3:部门-员工聚合关系

"""
department_aggregation.py
演示聚合:部门包含员工,但员工可以独立于部门存在
"""

class Employee:
    def __init__(self, emp_id, name):
        self.emp_id = emp_id
        self.name = name
    
    def __repr__(self):
        return f"Employee({self.emp_id}, {self.name})"

class Department:
    def __init__(self, dept_name):
        self.dept_name = dept_name
        self.employees = []   # 聚合:员工通过外部添加
    
    def add_employee(self, emp: Employee):
        self.employees.append(emp)
        print(f"{emp.name} 加入 {self.dept_name}")
    
    def remove_employee(self, emp: Employee):
        self.employees.remove(emp)
        print(f"{emp.name} 离开 {self.dept_name}")
    
    def count(self):
        return len(self.employees)
    
    def __repr__(self):
        return f"Department({self.dept_name}, 员工数:{self.count()})"

# 员工独立创建
alice = Employee(1001, "Alice")
bob = Employee(1002, "Bob")
charlie = Employee(1003, "Charlie")

# 部门1
dev_dept = Department("技术部")
dev_dept.add_employee(alice)
dev_dept.add_employee(bob)
print(dev_dept)

# 部门2
hr_dept = Department("人事部")
hr_dept.add_employee(charlie)

# 员工可以换部门
dev_dept.remove_employee(bob)
hr_dept.add_employee(bob)

# 删除部门,员工仍然存在(没有del调用)
del dev_dept
print(f"Alice仍然存在: {alice}")

案例4:组合优于继承——汽车与引擎/轮胎(组合 vs 继承)

"""
composition_vs_inheritance.py
比较使用继承和组合实现代码复用
"""

# 错误示例:通过继承来复用引擎功能
class Engine:
    def start(self):
        print("引擎启动")
    def stop(self):
        print("引擎停止")

class ElectricEngine(Engine):
    def charge(self):
        print("充电")

# 汽车继承引擎?这不合理,汽车不是一种引擎
class BadCar(Engine):
    def drive(self):
        self.start()
        print("行驶")
        self.stop()

# 正确示例:汽车组合引擎和轮胎
class GoodEngine:
    def start(self):
        print("引擎启动")
    def stop(self):
        print("引擎停止")

class ElectricEngine(GoodEngine):
    def charge(self):
        print("充电")

class Tire:
    def __init__(self, size):
        self.size = size
    def rotate(self):
        print(f"轮胎({self.size})旋转")

class GoodCar:
    def __init__(self, engine, tire_size=17):
        self.engine = engine   # 组合:引擎从外部传入
        self.tires = [Tire(tire_size) for _ in range(4)]
    
    def start(self):
        self.engine.start()
    
    def drive(self):
        self.start()
        for tire in self.tires:
            tire.rotate()
        print("汽车行驶")
    
    def stop(self):
        self.engine.stop()

# 使用组合:可以灵活替换引擎
gas_engine = Engine()
electric_engine = ElectricEngine()

car1 = GoodCar(gas_engine)
car2 = GoodCar(electric_engine, 18)

car1.drive()
car2.drive()

案例5:图形编辑器中的组合——ShapeGroup

"""
shape_group_composition.py
使用组合模式(也是一种组合关系)实现图形组
"""

from abc import ABC, abstractmethod

class Graphic(ABC):
    @abstractmethod
    def draw(self):
        pass
    
    @abstractmethod
    def move(self, dx, dy):
        pass

class Circle(Graphic):
    def __init__(self, x, y, r):
        self.x = x
        self.y = y
        self.r = r
    def draw(self):
        print(f"画圆 圆心({self.x},{self.y}) 半径{self.r}")
    def move(self, dx, dy):
        self.x += dx
        self.y += dy

class Rectangle(Graphic):
    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
    def draw(self):
        print(f"画矩形 左上({self.x},{self.y}) 宽{self.w}{self.h}")
    def move(self, dx, dy):
        self.x += dx
        self.y += dy

class Group(Graphic):
    """组合图形:可以包含多个图形,并作为一个整体操作"""
    def __init__(self):
        self.shapes = []
    
    def add(self, shape: Graphic):
        self.shapes.append(shape)
    
    def remove(self, shape: Graphic):
        self.shapes.remove(shape)
    
    def draw(self):
        for shape in self.shapes:
            shape.draw()
    
    def move(self, dx, dy):
        for shape in self.shapes:
            shape.move(dx, dy)

# 使用
c1 = Circle(10,10,5)
c2 = Circle(20,20,3)
r1 = Rectangle(30,30,10,20)

group1 = Group()
group1.add(c1)
group1.add(r1)

group2 = Group()
group2.add(c2)

root_group = Group()
root_group.add(group1)
root_group.add(group2)

print("原始位置")
root_group.draw()

root_group.move(5,5)
print("\n移动后")
root_group.draw()

案例6:依赖注入(依赖关系的解耦)

"""
dependency_injection.py
演示如何通过依赖注入降低耦合
"""

class EmailService:
    def send(self, to, subject, body):
        print(f"发送邮件给 {to}: {subject} - {body}")

class SMSService:
    def send(self, phone, message):
        print(f"发送短信给 {phone}: {message}")

class NotificationManager:
    # 依赖注入:通过构造器传入依赖
    def __init__(self, message_service):
        self.service = message_service   # 依赖抽象接口,不依赖具体实现
    
    def notify(self, recipient, content):
        # 假设统一接口 send
        self.service.send(recipient, "通知", content)

# 可以注入不同的服务
email_svc = EmailService()
sms_svc = SMSService()

notify_with_email = NotificationManager(email_svc)
notify_with_sms = NotificationManager(sms_svc)

notify_with_email.notify("user@example.com", "您的订单已发货")
notify_with_sms.notify("13800138000", "您的验证码是123456")

案例7:综合实战——汽车工厂(组合、聚合、依赖)

"""
car_factory_demo.py
综合使用多种关系构建汽车生产系统
"""

class Part:
    """零件基类"""
    def __init__(self, name):
        self.name = name

class Engine(Part):
    def start(self):
        print(f"{self.name} 引擎启动")

class Wheel(Part):
    def rotate(self):
        print(f"{self.name} 车轮旋转")

class Car:
    """汽车使用组合方式包含零件"""
    def __init__(self, model, engine_type, wheel_size):
        self.model = model
        # 组合:引擎和车轮在汽车内部创建
        self.engine = Engine(f"{model}-Engine")
        self.wheels = [Wheel(f"{model}-Wheel{i}") for i in range(4)]
        self.wheel_size = wheel_size   # 简单属性
    
    def drive(self):
        self.engine.start()
        for w in self.wheels:
            w.rotate()
        print(f"{self.model} 行驶")

class Worker:
    """工人,与工厂是聚合关系"""
    def __init__(self, name):
        self.name = name
    
    def assemble(self, car: Car):
        print(f"工人 {self.name} 正在组装 {car.model}")

class Factory:
    """工厂,包含工人(聚合),依赖零件供应商"""
    def __init__(self, name):
        self.name = name
        self.workers = []   # 聚合:工人可独立于工厂
    
    def hire(self, worker: Worker):
        self.workers.append(worker)
    
    def produce_car(self, model, engine_type, wheel_size):
        # 依赖:使用传入的零件参数(实际上零件在Car内部创建)
        car = Car(model, engine_type, wheel_size)
        # 随机找一个工人组装(依赖 Worker)
        if self.workers:
            worker = self.workers[0]
            worker.assemble(car)
        else:
            print("没有工人,无法生产")
        return car

# 创建工人
w1 = Worker("张三")
w2 = Worker("李四")

# 创建工厂
factory = Factory("特斯拉超级工厂")
factory.hire(w1)
factory.hire(w2)

# 生产汽车
car1 = factory.produce_car("Model 3", "electric", 18)
car2 = factory.produce_car("Model Y", "electric", 20)

car1.drive()
car2.drive()

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1混淆聚合和组合生命周期管理错误,可能导致内存泄漏或提前释放记住:组合强调整体部分同生共死,聚合部分可独立
2在组合关系中,从外部传入部分对象部分的生命周期不由整体控制,变成了聚合组合应该在整体内部创建部分,不通过参数传入
3依赖关系用实例变量持有提高了耦合度,关系变成了关联能通过参数传递就不要存储为成员变量
4过度使用继承,造成类层次过深代码难以维护,修改父类影响所有子类优先考虑组合,使用“策略”等设计模式
5双向关联导致循环引用垃圾回收困难,代码复杂度高尽量使用单向关联,必要时使用弱引用
6__del__中依赖组合对象析构顺序不确定,可能导致异常使用上下文管理器或显式关闭方法
7组合模式中忘记递归调用对组合对象的操作无法递归到子对象在组合类的方法中遍历所有子对象并调用相同方法
8依赖注入时没有提供接口抽象具体类变化导致多处修改依赖抽象基类或协议(Protocol)
9聚合中部分被多个整体共享时未处理共享语义删除一个整体可能意外删除部分使用引用计数或共享所有权概念
10误认为所有包含关系都是组合不适合的场景使用了强制生命周期耦合根据业务需求选择:部分离开整体还有意义吗?

📝 课后实战练习题

第1题:识别关系

分析以下代码片段,指出属于哪种关系(依赖、关联、聚合、组合、继承):

class A:
    def method(self, b): pass
class C:
    def __init__(self): self.d = D()
class E:
    def __init__(self, f): self.f = f
class G(H): pass

第2题:实现购物车的组合关系

定义CartItem(商品名、单价、数量)和ShoppingCart(包含__init__中创建空列表,提供添加、删除、计算总价)。确保购物车销毁时,CartItem也被销毁(虽然Python GC会处理)。然后实例化购物车,添加商品,打印总价。

第3题:班级与学生的聚合关系

定义Student类(姓名、学号)和Class类(班级名称,包含学生列表,提供添加、移除学生)。学生可以独立于班级存在。创建几个学生,加入班级,然后解散班级,学生仍然存在。

第4题:组合优于继承的重构

现有继承关系:Bird -> FlyBirdSwimBird等。尝试用组合模式重构:定义FlyBehaviorSwimBehavior接口,Bird组合这些行为。示例:一只企鹅(不能飞能游泳),一只燕子(能飞不能游泳)。

第5题:依赖注入实践

实现一个EmailSender接口,send(to, subject, body)。实现SmtpEmailSenderMockEmailSender(只打印)。写一个UserService类,通过构造函数注入EmailSender。在register_user方法中调用发送欢迎邮件。测试两种实现。

第6题:公司-部门-员工三层关系

设计Company(聚合部门)、Department(聚合员工)、Employee。要求:

  • 部门可以独立于公司存在(先创建部门再加入公司)。
  • 员工可以独立于部门存在。
  • 公司解散时,部门仍然存在(聚合);部门解散时,员工仍然存在。
  • 实现添加、移除、查询功能。

第7题:组合模式实现文件系统

使用组合模式实现文件和文件夹树:

  • Filenamesize,提供display(indent)
  • Foldernamechildren列表,可以addremovedisplay时递归调用子项
    测试创建一个包含子文件夹和文件的文件夹树,并显示整个结构。

🧠 知识点思维导图总结

第29课:类与类之间关系

六种关系(耦合度递增)

依赖

局部变量/方法参数

最弱关系

关联

持有引用

生命周期独立

单向/双向

聚合

整体-部分,部分独立

空心菱形

无生命周期控制

组合

整体-部分,同生共死

实心菱形

整体内部创建部分

泛化(继承)

is-a关系

实现(接口)

抽象基类

组合优于继承

继承缺点

白箱复用

父类变化影响子类

组合优点

黑箱复用

运行时动态绑定

符合接口隔离

代码实例

订单-订单项(组合)

部门-员工(聚合)

汽车-引擎(组合)

图形组合模式

面试考点

区别聚合与组合

如何实现组合

依赖注入与解耦


🔜 下节课预告

本课学习了类与类之间的关系,特别是组合和聚合。下一节课我们将进行面向对象综合实战,开发一个完整的人物信息管理系统,将封装、继承、多态、组合、设计模式等融会贯通。

第30课:OOP综合实战:人物信息管理系统控制台完整项目开发

内容包括:

  • 需求分析与类设计
  • 使用继承构建人物类型体系
  • 使用组合管理关系(如部门、联系人)
  • 实现完整的CRUD操作
  • 数据持久化(文件)
  • 菜单交互与异常处理

这将是一个完整的OOP项目,让你将所学知识融会贯通,达到进阶水平。

🌟 学习鼓励:理解类之间的关系是面向对象设计的进阶。组合和聚合虽然看起来简单,但在实际项目中正确应用它们能大大提升代码质量。请多思考“这个部分脱离整体还能独立存在吗?”从而选择组合还是聚合。动手完成练习,你将逐步掌握OOD的精髓。


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值