资讯动态

面向对象编程核心:类与对象、封装继承多态全解析

发布时间:2026/10/18 10:20:06 来源:尧图企业网站定制
在初学编程的阶段很多人都会在“类与对象”这一章感受到一种莫名的障碍。前面学变量、循环、函数时思路基本是顺的先做什么、再做什么、结果是什么。但一进入面向对象语法还是那些语法思维模式却突然要换一套。很多教程会直接甩出“类是一组对象的抽象”“对象是类的实例”这类定义背倒是能背真动手写却不知道怎么入手。我当年也有过这种阶段。后来带过几个项目、帮过不少入门者之后越来越确信一件事类与对象这章关键不是记住“什么是类”“什么是对象”这个结论而是理解“为什么要弄出这么一套东西出来”。把这个问题想通了后面那些构造方法、封装、继承、多态全部都是顺理成章的延伸。这篇内容我会从“面向对象到底想解决什么问题”讲起然后用完整的代码示例过一遍类与对象的实操过程再把封装、继承、多态这三个核心特征逐一拆解最后把初学阶段最容易踩的坑整理出来。演示代码用Python写但里面的道理放到Java、C、C#这些语言里完全通用。适合刚学完基础语法、准备进入面向对象章节的读者也适合学完一轮之后回来巩固概念的人。1. 面向对象到底在解决什么问题1.1 从面向过程的局限说起在接触类与对象之前绝大多数人的代码是这样的定义一堆变量写一堆函数然后按顺序调用。这就是典型的面向过程编程。问题不在这种方式“错了”而在于它的扩展能力有限。我常拿一个场景举例你写一个管理学生成绩的小程序一开始只需要存一个学生的姓名和分数用一个字典就够了。后来又需要存第二个学生、第三个学生于是你改成列表套字典。接着要算所有人的平均分、最高分你写了一个函数传进这个列表输出结果。到这一步一切还说得过去。但需求继续变化每个学生除了分数还有学号、班级、出勤记录、作业列表。列表里可能混入格式不同的数据。某个函数需要非常谨慎地处理这些结构稍微大意一点传错一个类型程序运行着运行着就崩了。更麻烦的是这些函数和数据结构之间没有明确的归属关系谁都能改改了之后影响范围也说不清楚。这就涉及面向对象要解决的核心问题把数据和对数据的操作绑定在一起打包成一个整体让它们之间形成清晰的边界。在Python里这个“整体”的模板就叫类根据类创建出来的具体实体就叫对象。1.2 类和对象的关系图纸与房子类与对象的关系最佳类比就是建筑图纸和实际房子。图纸上定义了房间数量、门窗位置、层高、材质但图纸本身不是可以住的房子。你需要按图纸施工一栋一栋地盖出来。每栋房子都是根据同一张图纸生产的但它们各自独立门坏了不会影响别的房子。把这个类比翻译到编程中类是定义描述“这类东西会长什么样、能做什么事情”对象是实体是类在内存中的具体实例。同一个类可以创建无数个对象每个对象都拥有类定义的属性和能力但对象之间的数据互不干扰。这个简单类比能帮你解决一个很实际的困惑为什么写代码时要先定义一个类然后再“实例化”才能用因为类本身只是一个蓝图Python解释器看到类的定义时只做了登记并没有真的分配出能存数据的空间。只有当你调用类名加括号像Student()这样才真正创建了一个对象。2. 类与对象的实操拆解2.1 定义一个类并创建对象先从最基本的代码写起。假设我们要定义一个“学生”类包含姓名和分数两个属性以及一个输出信息的方法。class Student: 学生类 def __init__(self, name, score): self.name name self.score score def show_info(self): print(f{self.name} 的成绩是 {self.score})创建对象并调用方法stu1 Student(张三, 92) stu1.show_info()这里有几个关键点必须理解清楚。__init__不是普通的函数它是构造函数在创建对象的那一刻被自动调用。它的作用是把传入的参数绑定到当前对象上。这个“当前对象”就是第一个参数self。很多刚入门的人会问为什么定义方法时第一个参数永远是self调用时却不用传因为Python在调用对象方法时会自动把这个对象本身作为第一个参数塞进去。stu1.show_info()等价于Student.show_info(stu1)。self这个名字不是语法强制规定的你换成this也能跑但几乎所有Python程序员都会用self这是约定俗成的规范没必要特立独行。2.2 构造方法后面的初始化细节构造方法__init__的名字是双下划线开头结尾这类方法在Python中有特殊身份被称为“魔术方法”或“特殊方法”。初学者不需要全部掌握但要知道一个原则只要你在类里定义了__init__创建对象时就必须提供它要求的参数否则解释器会直接报错。但有一类场景需要注意你希望某些属性可以暂时缺省。这时可以给参数设置默认值。class Student: def __init__(self, name, score0): self.name name self.score score这样Student(李四)也能创建成功分数默认为0。这个做法在真实项目里很常见比如部分信息后期再补全。还有一种坑是不要在构造方法里使用可变对象作为默认参数。比如def __init__(self, name, courses[]): self.courses courses这样看似没问题但默认列表[]在Python中只会被创建一次所有没传courses参数的实例会共享同一个列表。一个对象往里面添加了课程其他对象也能看到这个bug排查起来相当隐蔽。正确写法是默认用None在构造方法里再创建新列表。2.3 属性与方法的基本价值属性就是对象身上携带的数据方法的含义则是“这个对象能做什么”。在类与对象这个阶段你只需要把属性和方法想成两个篮子一个装数据一个装行为。继续扩展学生类增加一个判断是否及格的方法class Student: def __init__(self, name, score): self.name name self.score score def is_pass(self): return self.score 60 stu Student(王五, 58) print(stu.is_pass())这里is_pass方法内部通过self.score读取当前对象的分数。每一个对象调用这个方法时self都指向自己所以结果是各算各的。这就是“对象之间数据互不干扰”的具体体现。很多初学者会问既然方法里要读取分数为什么不直接写一个全局变量因为全局变量所有人都能改多个对象之间就没有边界了。面向对象强调的“归属感”就在这里score属于某个具体的学生对象show_info也属于这个对象只有通过这个对象才能访问它们。这种归属关系让代码结构更清晰也更好维护。3. 深入理解封装、继承、多态3.1 封装隐藏内部细节只留必要接口封装是面向对象最容易被误解的特性。很多人以为“封装”就是把代码写进类里其实没这么简单。封装的本质是控制外部对对象内部数据的访问权限把不该暴露的细节藏起来只提供必要的方法让外部操作数据。Python对权限的控制方式比较温和。它不像Java那样有private、public这样的关键字而是用命名约定来表达意图。class BankAccount: def __init__(self, owner, balance): self.owner owner self.__balance balance def deposit(self, amount): if amount 0: self.__balance amount def get_balance(self): return self.__balance这里__balance用了双下划线开头Python内部会做名称改写让外部直接访问account.__balance时抛异常。这就是一个“私有”属性。外部想查看余额要通过get_balance方法想存钱要通过deposit方法。为什么非要绕一层因为直接改属性可能出现非法状态。比如余额不能为负数如果外部直接account.balance -100程序也不会拦。通过方法操作数据就能在内部做校验。这就是封装的核心价值数据的安全由类自身来保证而不是寄希望于调用者自觉。实际开发中我一般建议不用过度设计。简单的小项目里属性直接公开也无妨。但涉及金额、状态、配置这类需要约束的数据必须封装起来否则后期排查数据被谁改乱了会非常痛苦。3.2 继承复用代码也带来了层级设计继承解决的是“多个类有重复代码”的问题。假设除了学生系统里还有老师。老师有姓名、工号、工资也要显示信息。如果各自写一个类就会有很多重复的属性定义和方法。这时可以把公共部分抽到一个父类里。class Person: def __init__(self, name): self.name name def show_info(self): print(f我叫{self.name}) class Student(Person): def __init__(self, name, score): super().__init__(name) self.score score class Teacher(Person): def __init__(self, name, salary): super().__init__(name) self.salary salary子类Student和Teacher自动拥有了name属性和show_info方法。它们的构造方法中通过super().__init__(name)调用父类的构造方法这样父类负责初始化它的属性子类只负责初始化自己新增的属性。继承带来的一个直观收益是修改公共逻辑时只需要改父类一处。比如所有人员都需要增加一个手机号字段只改Person类即可。这个收益在类多的时候会放大很多倍。不过也要提醒一点继承不是用得越多越好。过深的继承层次会让代码难以理解和维护。通常能用组合解决的就不要强行套继承。组合的意思是“对象里包含另一个对象”比如“学生”可以包含一个“联系方式”对象而不是让“联系方式”去继承“学生”。很多设计上的纠结核心都是“继承还是组合”的选择建议初学时先掌握继承的基本用法后面做设计分析时再慢慢体会。3.3 多态同一动作在不同对象上有不同表现多态的前提是继承。它的意思是不同类的对象对同一个方法名有各自的实现调用方不需要关心对象的具体类型直接调用就行。沿用上面的例子假设一套系统里存了各种人员统一放到一个列表里。遍历列表时每个人调用show_info输出的内容各不相同。people [ Student(张三, 92), Teacher(李老师, 8000), ] for person in people: person.show_info()如果你在Student和Teacher中都重写了show_info方法那么每个对象调用时都会执行自己的版本。这就是多态。放到业务场景里最常见的用法是定义统一的接口让不同实现类各自完成逻辑上层代码不需要写一堆if isinstance(...)判断类型。Python的多态是“鸭子类型”风格只要一个对象具备某个方法就能被调用并不严格要求它继承自某个父类。所以Python中多态的使用更加灵活但这也意味着你需要在设计时更加自律保证不同类的同名方法在语义上一致否则代码会变得难以预测。4. 实战演练从需求到类的完整流程4.1 一个贴近真实的模拟项目为了把上面的知识点串起来我设计一个模拟项目图书管理系统的核心部分。需求很简单系统要能添加图书、借出图书、归还图书并且记录每本书当前的状态。第一版先定义图书类class Book: def __init__(self, title, author): self.title title self.author author self.is_borrowed False def borrow(self): if self.is_borrowed: raise ValueError(这本书已被借出) self.is_borrowed True def return_book(self): if not self.is_borrowed: raise ValueError(这本书没有被借出) self.is_borrowed False def show_status(self): state 已借出 if self.is_borrowed else 在馆 print(f《{self.title}》- {self.author} 当前状态{state})这里is_borrowed是内部状态外部通过borrow和return_book来操作。borrow方法里先检查状态避免“重复借出”return_book避免“重复归还”。这种把状态校验放在类内部的做法正是封装思想的直接应用。然后写一个简单的管理类用来管理多本图书class Library: def __init__(self): self.books [] def add_book(self, book): self.books.append(book) def borrow_book(self, title): for book in self.books: if book.title title: book.borrow() print(f借出成功《{book.title}》) return print(f找不到《{title}》) def show_all(self): if not self.books: print(图书馆目前没有藏书) return for book in self.books: book.show_status()运行逻辑lb Library() b1 Book(三体, 刘慈欣) b2 Book(活着, 余华) lb.add_book(b1) lb.add_book(b2) lb.borrow_book(三体) lb.show_all()这个项目规模虽然小但把类的组织方式、对象之间的协作、内部状态的管理都覆盖到了。Library持有一个Book对象列表调用Book对象的方法来完成操作。这就是类与对象之间最常见的协作关系。4.2 初学阶段的高频错误排查我整理了几个初学类与对象时特别容易踩的坑很多错误到了一定经验之后看起来可笑但对刚开始写的人来说极其困惑。第一个忘了加self。定义方法时如果参数列表里没有self创建对象后调用方法会立刻报错missing 1 required positional argument: self。原因前面说过Python调用对象方法时会自动把对象传进去。这个错误几乎人人都会遇到一旦理解了self的机制就再也不会犯。第二个在方法内部给对象添加了不属于类定义的属性。比如stu.gender 男。Python允许这样做但这会让同一个类的不同对象拥有的属性集合不一致。调试时访问某个对象的属性可能发现它不存在。这种随意添加属性的习惯在项目规模变大之后非常危险。建议属性都放在__init__中集中定义保持类的结构稳定。第三个继承时没有调用父类的构造方法。子类定义了自己的__init__但忘了写super().__init__(...)。结果是父类的属性根本没有初始化子类里访问self.name时直接报错。这是继承写法里最高频的问题没有之一。第四个把可变对象作为方法的默认参数。这个问题在2.2节里提到过但值得再次强调。它的bug非常隐蔽程序表面能运行数据却悄悄共享了。凡是默认参数需要用到列表、字典这类可变类型时一律先用None占位再在内部创建新对象。def __init__(self, name, tagsNone): self.name name self.tags [] if tags is None else tags第五个分不清类属性和实例属性。直接定义在类内部的变量属于类本身所有实例共享在__init__中用self.xxx定义的是实例属性每个实例独立。名称相同的情况下实例属性会覆盖类属性。这个机制了解即可但初学阶段最好统一通过self定义实例属性避免混淆。5. 一些学习和设计的实用建议类与对象这套东西光靠看是学不会的。我见过的很多人看完书感觉全会一写代码还是不会。根源在于缺乏“识别需求中的对象”的训练。每次拿到一个需求先问自己三个问题有哪些明显的名词每个名词的职责是什么名词之间是什么关系这个过程做得多了类的设计能力自然就上来了。有一个比较实用的练习方法找一个你自己用过的小工具比如记账本、待办清单、通讯录试着用类去重构它。不需要重新实现全部功能只要把核心实体抽象成类就行。比如记账本里有账单、分类、账户三个类待办清单里有任务、清单两个类。这个练习比抄十遍语法都更管用因为它逼着你从需求出发而不是从代码出发。另外在语法层面Python提供了一些方便的工具初学者可以适当了解。比如property装饰器可以把方法调用变成属性访问是封装的一种优雅实现dataclasses模块可以用来快速定义以数据为主的类。但这些东西不建议一上来就全部堆进代码里容易分散对核心概念的注意力。先把类、对象、构造方法、方法调用这些基本功打得扎扎实实再谈进阶技巧。关于面向对象的“度”我的体会是不是所有代码都必须套类。写一个几十行的脚本函数式写法可能更直接。类的优势在项目规模变大、状态变多、协作变复杂之后才真正体现出来。所以判断一个设计好不好标准很简单它是否让代码更容易理解、更容易修改。如果套了一层又一层类反而让人看不明白那这个设计大概率是过度工程。最后再分享一个小技巧。我调试类的相关代码时经常在__init__里临时打印一行日志确认对象创建时各参数的传入情况排查结束后再删掉。这个方法看起来土但对于理解对象的初始化过程、定位属性缺失问题非常有效。初学阶段不要只依赖断点调试多试试这种“笨办法”你对程序的执行流程会建立更扎实的直觉。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑