☰
Python面向对象入门:从图书管理实战理解封装、继承与多态
2026/9/30 4:43:04 网站建设 项目流程

1. 为什么“面向对象”不是Python新手该急着啃的硬骨头

很多人打开Python教程,翻到“面向对象”这一章,第一反应是:终于要学真本事了。接着被class、self、__init__、继承、多态、抽象类、元类……一连串术语砸得头晕眼花,抄几行代码跑通了,但心里发虚——这到底解决了什么实际问题?为什么非得这么写?我用函数不也挺好?

我带过三十多个零基础转行的学员,其中八成在“面向对象”章节卡壳超过两周。不是他们笨,而是绝大多数教程犯了一个根本性错误:把面向对象当成一种语法规范来教,而不是一种问题建模工具。结果就是,学生记住了def __str__(self): return f"{self.name}",却不知道什么时候该重写它;能背出“封装、继承、多态”三个词,但面对一个真实的业务需求——比如写一个简易图书管理系统——依然本能地用一堆全局变量和函数堆砌,最后发现数据错乱、改一处崩三处。

这背后的真实逻辑是:面向对象的本质,是把现实世界中的“事物”及其“行为”,映射成代码中可复用、可维护、可协作的单元。它不是为了炫技,而是为了解决“代码越来越难改、越来越不敢动”的工程熵增问题。
你不需要一上来就理解__new__和__metaclass__,但必须清楚:当你的程序里开始出现重复的参数传递(比如每个函数都要传user_id,db_conn,config)、当某个数据结构被七八个函数反复解析和修改、当你想加一个新功能却要改遍十几个地方——这时候,面向对象就不是“该不该学”,而是“再不学就要崩溃了”。

所以这篇“超详版”,不按教科书顺序罗列概念。我们从一个真实、微小、但每天都在发生的场景切入:管理三本图书的信息。先用最原始的函数方式写,然后一步步暴露它的痛点,再自然引出类、属性、方法、封装、继承……每一个设计决策,都对应一个具体、可感知的“痛”。你看完会明白:class Book:不是一句魔法咒语,而是你为解决“数据和操作绑在一起太松散”这个问题,亲手画下的一条边界线。

提示:本文所有代码均基于 Python 3.8+,无需额外安装依赖。建议你边读边在本地 IDLE 或 VS Code 中敲一遍——不是为了复制粘贴,而是为了感受每一次重构带来的“松一口气”的感觉。

2. 从三本图书开始:函数式写法的甜蜜陷阱与隐性成本

假设你要写一个极简的图书信息展示程序,只管三本书:《深入理解计算机系统》、《算法导论》、《设计模式》。每本书有书名、作者、页数、是否已读。最直觉的写法,是用字典存数据,用函数处理:

# 方式一:纯字典 + 函数 book1 = {"title": "深入理解计算机系统", "author": "Randal Bryant", "pages": 748, "read": True} book2 = {"title": "算法导论", "author": "Thomas Cormen", "pages": 1312, "read": False} book3 = {"title": "设计模式", "author": "Erich Gamma", "pages": 395, "read": True} def print_book_info(book): status = "已读" if book["read"] else "未读" print(f"《{book['title']}》 | {book['author']} | {book['pages']}页 | {status}") def mark_as_read(book): book["read"] = True def get_reading_progress(books): total = len(books) read_count = sum(1 for b in books if b["read"]) return f"共{total}本,已读{read_count}本,进度{read_count/total*100:.1f}%" # 使用 print_book_info(book1) mark_as_read(book2) print(get_reading_progress([book1, book2, book3]))

这段代码跑起来完全没问题,输出清晰,逻辑简单。初学者看到这里,会觉得:“这不挺好吗?干嘛还要搞个 class?” —— 这正是陷阱所在。它的“好”,只存在于当前这个三本书、三个函数、没有并发、没有扩展需求的真空环境里。一旦你尝试做一点点延伸,裂缝立刻出现。

2.1 第一次裂痕:数据与操作分离导致的“传参疲劳”

现在需求升级:要支持“借阅”功能,记录借阅人和借出日期。你得给每个函数都加参数:

# 升级后:增加借阅人和日期 book1["borrower"] = "张三" book1["borrow_date"] = "2024-03-15" def print_book_info_extended(book, borrower=None, borrow_date=None): # ... 大量if判断处理可选参数 pass def mark_as_read_extended(book, borrower=None, borrow_date=None): # ... 同样要处理可选参数 pass

问题来了:borrower和borrow_date是book1的专属属性,但print_book_info_extended函数却要为所有书都预留这两个参数。更糟的是,如果某本书没借出,你传None还得在函数里写一堆if borrower is not None:判断。数据本该附着在它所属的实体上,但现在却被拆散,像散落一地的零件,每次组装都要手动对齐。

2.2 第二次裂痕:数据结构变更引发的“连锁地震”

老板说:“页数太粗略,要区分‘正文页数’和‘附录页数’。” 你改字典:

book1 = { "title": "深入理解计算机系统", "author": "Randal Bryant", "pages": {"main": 680, "appendix": 68}, # 改成嵌套字典 "read": True }

恭喜,所有用到book["pages"]的地方——print_book_info、get_reading_progress(如果它计算总页数)、甚至未来可能写的get_total_pages()——全部报错。你得逐个打开函数,把book["pages"]替换成book["pages"]["main"] + book["pages"]["appendix"]。一个数据结构的微调,触发了全代码库的“雪崩式”修改。这就是“高耦合”的典型症状:数据和它的使用方式,像胶水一样死死粘在一起,撕开一个,整个结构就散架。

2.3 第三次裂痕:无法约束数据的“信任危机”

最隐蔽也最危险的问题是:字典是开放的,你无法阻止别人往里面塞错东西。比如:

book1["pages"] = "七百四十八页" # 字符串,不是数字 book2["read"] = "yes" # 字符串,不是布尔值 book3["author"] = ["Erich Gamma", "Richard Helm"] # 列表,不是字符串

这些操作在 Python 里完全合法,不会报错。但后续所有依赖book["pages"]是整数、book["read"]是布尔值的函数,都会在运行时突然崩溃。你无法在代码层面建立一道防线,只能靠程序员自觉和文档约定——而现实是,文档永远滞后,自觉永远不可靠。

注意:这三个裂痕,就是面向对象要解决的核心问题。它们不是理论空谈,而是你在项目迭代中必然撞上的墙。所谓“面向对象编程”,本质上就是一套应对这三种裂痕的、经过时间验证的工程实践方案。

3. 封装:用 class 画一条“数据与行为”的楚河汉界

现在,我们不再把数据(书名、作者)和行为(打印信息、标记已读)分开存放和调用,而是把它们打包进一个独立的、有名字的容器里。这个容器,就是class。

3.1 最朴素的 class:只是把字典和函数“物理打包”

# 方式二:最简 class 封装 class Book: def __init__(self, title, author, pages, read=False): self.title = title self.author = author self.pages = pages self.read = read def print_info(self): status = "已读" if self.read else "未读" print(f"《{self.title}》 | {self.author} | {self.pages}页 | {status}") def mark_as_read(self): self.read = True # 创建实例 book1 = Book("深入理解计算机系统", "Randal Bryant", 748, True) book2 = Book("算法导论", "Thomas Cormen", 1312, False) book3 = Book("设计模式", "Erich Gamma", 395, True) # 使用 book1.print_info() # 直接调用,无需传入 book1 自身 book2.mark_as_read()

看懂了吗?Book这个类,就像一个模具。__init__方法是模具的“浇筑口”,你把原料(title,author...)倒进去,它就自动给你产出一个具体的、有血有肉的“图书实例”(book1,book2)。而print_info和mark_as_read这些方法,不再是独立的函数,它们是这个实例自带的“技能”,调用时直接book1.print_info(),self参数由 Python 自动传入,指向book1本身。

这一步的革命性在于:数据(self.title)和操作数据的代码(print_info),被物理地、强制地绑定在同一个命名空间里。你不能再随意给book1塞一个book1["borrower"],因为book1不是字典,它是Book类的一个实例,它的属性只能通过book1.title、book1.author这样的点号访问。这天然就解决了“数据与操作分离”的问题。

3.2 封装的深层价值:隐藏实现细节,暴露稳定接口

封装不只是“打包”,更是“划界”。它定义了什么是“内部”,什么是“外部”。我们来强化这个边界:

# 方式三:带私有属性和 getter 的封装 class Book: def __init__(self, title, author, pages, read=False): self._title = title # 下划线开头,约定为“受保护”属性 self._author = author self._pages = pages self._read = read # 提供公共接口,控制对内部数据的访问 @property def title(self): return self._title @title.setter def title(self, value): if not isinstance(value, str) or not value.strip(): raise ValueError("书名必须是非空字符串") self._title = value.strip() @property def pages(self): return self._pages @pages.setter def pages(self, value): if not isinstance(value, int) or value <= 0: raise ValueError("页数必须是正整数") self._pages = value def print_info(self): status = "已读" if self._read else "未读" print(f"《{self._title}》 | {self._author} | {self._pages}页 | {status}") # 使用 book = Book("算法导论", "Cormen", 1312) print(book.title) # 正常获取 book.title = "算法导论(第3版)" # 通过 setter 验证后赋值 # book._title = "乱改" # 虽然技术上可行,但违反约定,不推荐

这里引入了@property装饰器。它让title看起来像一个普通属性(book.title),但背后可以执行任意逻辑(比如类型检查、数据清洗)。这意味着,外部使用者永远只和title这个“门面”打交道,而Book类内部可以自由决定_title是怎么存储、怎么计算的。今天_title是字符串,明天你可以改成一个包含拼音、首字母索引的复杂对象,只要title这个接口不变,所有调用它的代码都不用改。

这就是封装的终极目标:降低模块间的依赖强度。当Book类的内部实现像乐高积木一样可以随意替换时,整个系统的稳定性才真正建立起来。

3.3 实操心得:别一上来就追求“完美封装”,先让代码可读

很多新手一学封装,就想把所有属性都加上@property和复杂的校验。这是误区。我的经验是:封装的粒度,应该和你的业务风险匹配。对于一个个人读书笔记小程序,title是字符串就够了,强行加校验反而让代码臃肿。但对于一个需要对接出版社 API 的图书管理系统,isbn属性就必须有严格的格式校验(13位数字、符合EAN-13规则),否则脏数据会一路污染到数据库。

所以,我的建议是:

  • 第一步:用class把数据和方法物理打包。这是最核心、收益最大的一步。
  • 第二步:识别哪些属性是“关键业务字段”,给它们加@property和基础校验。比如isbn,price,publish_date。
  • 第三步:只有当业务逻辑变得复杂(比如price需要根据会员等级动态计算),才考虑把 getter 变成真正的计算属性。

记住,self._title的下划线约定,不是 Python 的强制语法,而是一种团队沟通的“暗号”。它告诉其他开发者:“这个东西,最好别直接碰,用上面的title接口。” 这种约定的力量,在大型团队协作中,远胜于任何技术上的绝对禁止。

4. 继承:让“子类”自动获得“父类”的能力,避免重复造轮子

现在,我们的Book类很完美,但业务又变了:除了普通图书,还需要管理“电子书”(eBook),它有额外的属性:file_size_mb(文件大小)、format(格式,如 PDF、EPUB)。你当然可以再写一个EBook类,把title,author,pages,read全部复制一遍……但这违背了 DRY(Don't Repeat Yourself)原则,而且一旦Book类的print_info方法要加个“出版年份”,你得同步改两个地方。

继承,就是为了解决这种“相似但有差异”的代码复用问题。

4.1 继承的语法与本质:is-a 关系的代码表达

# 方式四:继承 class Book: def __init__(self, title, author, pages, read=False): self.title = title self.author = author self.pages = pages self.read = read def print_info(self): status = "已读" if self.read else "未读" print(f"《{self.title}》 | {self.author} | {self.pages}页 | {status}") def mark_as_read(self): self.read = True class EBook(Book): # EBook 继承自 Book def __init__(self, title, author, pages, file_size_mb, format_type, read=False): super().__init__(title, author, pages, read) # 调用父类的 __init__ self.file_size_mb = file_size_mb self.format = format_type def print_info(self): # 重写父类方法 super().print_info() # 先调用父类的打印 print(f" [电子书] {self.file_size_mb}MB | {self.format}") # 使用 book = Book("设计模式", "Gamma", 395) ebook = EBook("Python Cookbook", "David Beazley", 706, 5.2, "PDF") book.print_info() # 《Python Cookbook》 | David Beazley | 706页 | 未读 ebook.print_info() # 《Python Cookbook》 | David Beazley | 706页 | 未读 # [电子书] 5.2MB | PDF

关键点解析:

  • class EBook(Book):表明EBook是Book的一种。这是一种is-a 关系(电子书是一种图书),这是继承的语义基础。如果关系是 “has-a”(比如“图书馆有图书”),那就该用组合(Composition),而不是继承。
  • super().__init__(...)是调用父类构造函数的标准写法。它确保EBook实例也拥有title,author等所有Book的属性。
  • ebook.print_info()调用的是EBook类里重写的版本,而book.print_info()调用的是Book类的原始版本。Python 的方法解析顺序(MRO)自动完成了这个分发。

4.2 继承的威力:一次修改,全域生效

假设现在要给所有图书加一个“评分”功能。你只需要在Book类里加:

class Book: # ... 其他代码不变 ... def __init__(self, title, author, pages, read=False, rating=0.0): self.title = title self.author = author self.pages = pages self.read = read self.rating = rating # 新增评分属性 def set_rating(self, score): if 0.0 <= score <= 5.0: self.rating = score else: raise ValueError("评分必须在0.0到5.0之间") def print_info(self): status = "已读" if self.read else "未读" rating_str = f" | 评分: {self.rating}/5.0" if self.rating > 0 else "" print(f"《{self.title}》 | {self.author} | {self.pages}页 | {status}{rating_str}")

神奇的事情发生了:EBook类不需要做任何修改,它自动拥有了rating属性和set_rating方法!因为EBook继承了Book的所有成员。你创建一个EBook实例,就可以直接ebook.set_rating(4.5)。

这就是继承带来的可维护性红利。你把通用逻辑放在父类,把特有逻辑放在子类,系统就像一棵树,根部(父类)的每一次强壮,都会让所有枝叶(子类)受益。

4.3 继承的陷阱:菱形继承与过度设计

继承虽好,但滥用会带来灾难。最常见的坑是“菱形继承”:

class A: def method(self): print("A") class B(A): def method(self): print("B") super().method() class C(A): def method(self): print("C") super().method() class D(B, C): # D 同时继承 B 和 C pass d = D() d.method() # 输出什么?

答案是:B,C,A。Python 用 C3 线性化算法确定 MRO(Method Resolution Order),确保每个父类只被调用一次。但如果你没理清B和C的职责,D的行为就会变得难以预测。

更普遍的陷阱是“过度继承”。比如,为了管理“杂志”,你又建一个Magazine(Book),为了管理“报纸”,再建Newspaper(Book)……最后发现Book变成了一个臃肿的“万能基类”,里面塞满了各种if isinstance(self, Magazine): ...的判断。这说明模型错了。

我的避坑经验:

  • 问自己:子类是否真的“是”父类的一种?如果答案是“它包含了父类”,那就该用组合(Composition)。例如,“图书馆”包含“图书”,所以Library类里应该有一个books列表属性,而不是继承Book。
  • 优先用组合,谨慎用继承。组合关系更灵活,更容易测试,也更符合“高内聚、低耦合”的设计原则。
  • 继承层级不要超过三层。Animal -> Mammal -> Dog是合理的;Book -> PhysicalBook -> HardcoverBook -> LimitedEditionHardcoverBook就大概率是过度设计。

5. 多态:同一份代码,适配多种对象,让扩展变得“无感”

多态(Polymorphism)是面向对象的第三块基石。它的核心思想是:你写一段代码,它能自动适应不同类型的对象,只要这些对象提供了相同名称的接口(方法)。这让你的代码具备了强大的扩展性。

5.1 多态的直观体现:一个函数,处理所有“图书”

回到最初的需求:统计阅读进度。之前我们用函数处理字典列表:

def get_reading_progress(books): total = len(books) read_count = sum(1 for b in books if b["read"]) return f"共{total}本,已读{read_count}本,进度{read_count/total*100:.1f}%"

现在,我们有Book和EBook两种实例。它们的read属性名是一样的,但它们是不同的类。多态让我们可以这样写:

def get_reading_progress(items): """items 可以是 Book 列表,也可以是 EBook 列表,甚至混合列表""" total = len(items) # 关键:统一调用 .read 属性,不管它是 Book 还是 EBook read_count = sum(1 for item in items if item.read) return f"共{total}本,已读{read_count}本,进度{read_count/total*100:.1f}%" # 混合使用 books = [ Book("设计模式", "Gamma", 395, True), EBook("流畅的 Python", "Luciano Ramalho", 768, 8.1, "PDF", False), Book("代码大全", "Steve McConnell", 914, True) ] print(get_reading_progress(books)) # 共3本,已读2本,进度66.7%

看,get_reading_progress函数完全不知道items里装的是什么。它只认一个规则:item.read。只要item有这个属性(或者有同名的@property),它就能工作。这就是多态的魔力——它解耦了“做什么”和“怎么做”。函数只关心“我要读取read状态”,而Book和EBook各自负责“我如何提供这个状态”。

5.2 多态的进阶:抽象基类(ABC)——强制子类实现接口

上面的例子依赖于“鸭子类型”(Duck Typing):如果一个对象走路像鸭子、叫起来像鸭子,那它就是鸭子。这很灵活,但也埋下隐患:如果某个子类忘了实现read属性,程序会在运行时才报错。

Python 提供了abc模块,让我们能定义“抽象基类”,强制子类必须实现某些方法:

from abc import ABC, abstractmethod class ReadableItem(ABC): # 抽象基类 @abstractmethod def is_read(self): """返回 True 表示已读,False 表示未读""" pass @abstractmethod def get_title(self): """返回书名""" pass class Book(ReadableItem): def __init__(self, title, author, pages, read=False): self._title = title self._author = author self._pages = pages self._read = read def is_read(self): # 必须实现! return self._read def get_title(self): # 必须实现! return self._title class EBook(ReadableItem): def __init__(self, title, author, pages, file_size_mb, format_type, read=False): self._title = title self._author = author self._pages = pages self._file_size_mb = file_size_mb self._format = format_type self._read = read def is_read(self): # 必须实现! return self._read def get_title(self): # 必须实现! return self._title # 尝试实例化抽象基类,会报错! # item = ReadableItem() # TypeError: Can't instantiate abstract class... # 现在,get_reading_progress 可以更安全地写: def get_reading_progress(items): total = len(items) # 确保 items 中的每个元素都是 ReadableItem 的子类实例 read_count = sum(1 for item in items if item.is_read()) return f"共{total}本,已读{read_count}本,进度{read_count/total*100:.1f}%"

@abstractmethod就像一份契约。ReadableItem说:“所有继承我的类,必须提供is_read()和get_title()这两个方法,否则不许出生。” 这让 IDE 能提前提示错误,也让团队协作有了明确的接口规范。

5.3 多态的实战价值:插件式架构的雏形

多态最强大的应用,是构建“插件式”系统。想象一个图书管理系统,未来要支持从不同来源导入数据:本地 CSV 文件、豆瓣 API、微信读书 API。每个来源的数据结构不同,但最终都要变成Book或EBook对象。

你可以定义一个抽象的DataSource类:

class DataSource(ABC): @abstractmethod def fetch_books(self) -> list[ReadableItem]: """从数据源获取图书列表""" pass class CSVDataSource(DataSource): def fetch_books(self) -> list[ReadableItem]: # 解析 CSV,返回 Book/EBook 实例列表 pass class DoubanAPISource(DataSource): def fetch_books(self) -> list[ReadableItem]: # 调用豆瓣 API,返回 Book/EBook 实例列表 pass # 主程序只需关心 DataSource 接口 def load_books_from_source(source: DataSource): books = source.fetch_books() for book in books: print(f"加载: {book.get_title()} ({'已读' if book.is_read() else '未读'})") return books # 扩展新数据源?只需写一个新的类,继承 DataSource,实现 fetch_books # 主程序 load_books_from_source(...) 完全不用改!

这就是多态赋予你的力量:主干逻辑(load_books_from_source)是稳定的,变化的部分(数据源)被隔离在独立的、可插拔的模块里。这种设计,是大型软件系统可维护、可扩展的生命线。

6. 特殊方法(Magic Methods):让自定义类像内置类型一样自然

Python 的内置类型(int,str,list)之所以用起来舒服,是因为它们重载了各种运算符和内置函数。比如len(my_list)、my_str.upper()、a + b、obj in container。你的自定义类,也可以做到。

这些特殊方法,以双下划线开头和结尾(如__len__,__str__,__add__),被称为“魔术方法”(Magic Methods)。它们不是让你炫技的,而是为了让类的实例能无缝融入 Python 的生态系统。

6.1__str__和__repr__:让 print 和调试更友好

class Book: def __init__(self, title, author, pages, read=False): self.title = title self.author = author self.pages = pages self.read = read def __str__(self): """用户友好的字符串表示,用于 print()""" status = "已读" if self.read else "未读" return f"《{self.title}》 by {self.author} ({status})" def __repr__(self): """开发者友好的字符串表示,用于调试和日志""" return f"Book(title='{self.title}', author='{self.author}', pages={self.pages}, read={self.read})" book = Book("算法导论", "Cormen", 1312, False) print(book) # 《算法导论》 by Cormen (未读) <- 调用 __str__ print(repr(book)) # Book(title='算法导论', author='Cormen', pages=1312, read=False) <- 调用 __repr__

区别在于:

  • __str__是给最终用户看的,要简洁、易懂、有温度。
  • __repr__是给程序员看的,要精确、无歧义、能还原对象(理想情况下,eval(repr(obj)) == obj)。

6.2__len__,__getitem__,__contains__:让类支持序列操作

class Library: def __init__(self): self._books = [] def add_book(self, book): self._books.append(book) def __len__(self): """支持 len(library)""" return len(self._books) def __getitem__(self, index): """支持 library[0], library[1:3]""" return self._books[index] def __contains__(self, item): """支持 'book' in library""" return item in self._books def __iter__(self): """支持 for book in library""" return iter(self._books) # 使用 lib = Library() lib.add_book(Book("设计模式", "Gamma", 395)) lib.add_book(Book("代码大全", "McConnell", 914)) print(len(lib)) # 2 print(lib[0].title) # 设计模式 print(Book("设计模式", "Gamma", 395) in lib) # True for book in lib: # 可迭代 print(book.title)

现在,Library类的行为,已经和list非常接近了。你不需要教用户“调用lib.get_books()”,用户直觉就会用len(),[],in,for这些熟悉的语法。这降低了学习成本,提升了代码的可读性和一致性。

6.3__eq__和__hash__:让对象能正确比较和用作字典键

默认情况下,两个Book实例即使内容完全一样,==也会返回False,因为它们是不同的内存地址。

book1 = Book("设计模式", "Gamma", 395) book2 = Book("设计模式", "Gamma", 395) print(book1 == book2) # False # 添加 __eq__ 和 __hash__ class Book: # ... __init__ 等 ... def __eq__(self, other): if not isinstance(other, Book): return NotImplemented return (self.title == other.title and self.author == other.author and self.pages == other.pages) def __hash__(self): # 基于不可变属性计算 hash,确保相等的对象 hash 也相等 return hash((self.title, self.author, self.pages)) # 现在 book1 = Book("设计模式", "Gamma", 395) book2 = Book("设计模式", "Gamma", 395) print(book1 == book2) # True print({book1, book2}) # {<Book object>},集合里只有一个元素

__hash__的存在,让Book实例可以作为dict的 key 或set的元素。这对于去重、快速查找等场景至关重要。

提示:__eq__和__hash__必须保持一致。如果a == b为True,那么hash(a)必须等于hash(b)。通常,__hash__应该基于__eq__中用到的那些不可变属性来计算。

7. 面向对象的“道”与“术”:何时该用,何时该克制

学到这里,你已经掌握了面向对象的大部分“术”:封装、继承、多态、特殊方法。但真正的高手,懂得在合适的时机,用最恰当的工具。面向对象不是银弹,它有其适用的“道”。

7.1 该用面向对象的四个明确信号

  1. 状态(State)与行为(Behavior)高度耦合。
    如果你的数据(比如一个用户的配置)需要被多个函数反复读写,并且这些函数的逻辑都围绕这个数据展开,那么把它封装成一个类,是天经地义的选择。class UserConfig:比user_config_dict加一堆update_config(),validate_config()函数,要清晰得多。

  2. 需要创建多个具有相同结构、不同数据的“个体”。
    Book,Car,Customer,Order……这些都是典型的“实体”。每个实例都有自己的属性(title,model,name,order_id)和方法(print_info(),start_engine(),place_order())。用类来建模,是思维最自然的映射。

  3. 存在明显的“is-a”或“has-a”关系,且需要复用或组合。
    Dogis-aAnimal;Carhas-aEngine。当这种关系清晰,并且能带来代码复用或结构清晰的好处时,继承或组合就是最佳选择。

  4. 系统需要长期维护和扩展,且变更点集中在特定领域。
    比如,一个电商系统,支付方式(支付宝、微信、PayPal)经常变化。把每种支付方式做成一个类,都实现pay(amount)接口,主程序只依赖这个接口。未来加新支付方式,只需新增一个类,主流程零修改。这就是面向对象带来的“开闭原则”(对扩展开放,对修改关闭)。

7.2 该克制面向对象的三个常见场景

  1. 纯粹的工具函数(Utility Functions)。
    def calculate_tax(amount, rate): return amount * rate。它没有状态,输入决定输出,就是一个数学公式。把它塞进一个TaxCalculator类里,只会徒增复杂度。Python 的模块(.py文件)就是最好的工具函数容器。

  2. **一次性脚本或数据

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询