📄 一句话概括
抛开比喻,对象只有状态、行为、标识三样;凑齐这三样就是对象,不需要现实原型。
📝 本节和前面几节的关系
2.6 拆掉了比喻,这一节给正式定义——从这一节起,讨论都以代码为准。
一、三要素
状态——实例变量,它现在是什么样。 行为——方法,它能做什么。 标识——它在内存里的地址,它是【哪一个】。 三样凑齐,它就是一个对象。一条都不能少:只有状态没有行为,那是数据结构。
二、怎么验证
实测:vars(c) 给出状态 {'n': 2}。
实测:dir(c) 里去掉下划线开头的、且能调用的,剩下 ['add']——这就是行为。
实测:id(c) 给出标识,一个每次运行都不同的数。
注意 id 那一栏每次都不一样——它只是这个物的编号,不是内容。
三、没有现实原型的对象
Session(会话)——现实里指不出它是哪个物。
Transaction(事务)——同样指不出。
Mutex(互斥锁)——更抽象,它连【东西】都不算,是一种【约定】。
但它们在代码里都是标准的对象:有状态、有行为、有标识。
四、定义带来的自由
既然不要求现实原型,那就可以放心地为【软件自己的需要】造概念。 缓存、连接池、事件总线、仓储——这些都不是现实里的物,却是好软件的必需品。 反过来也成立:现实里有的东西,不一定要建成类。 判断标准只有一条:它在这个系统里有没有【状态+行为】需要维护。
五、动手 Demo:明确定义为编程结构
造一个最简单的计数器对象,把三要素逐一验出来。
状态看 vars(),行为看 dir() 里能调用的,标识看 id()。
class Counter:
def __init__(self): self.n = 0
def add(self):
self.n += 1
return self.n
c = Counter(); c.add(); c.add()
print("状态", vars(c))
print("行为", [m for m in dir(c)
if not m.startswith("_") and callable(getattr(c, m))])
print("标识", id(c) % 1000000)
实跑输出:
--------------------------------------------------------------------
明确定义为编程结构
--------------------------------------------------------------------
抛开比喻,对象在编程里只有三样东西:**状态、行为、标识。**
class Counter: n / add()
c = Counter(); c.add(); c.add()
要素 在代码里是什么 实测值
---------------------------------------------
状态 实例变量 {'n': 2}
行为 方法 ['add']
标识 内存地址 id 末六位 482432
**这三样凑齐,它就是一个对象——不需要它在现实里对应什么东西。**
标识那一栏每次运行都不一样,**它只是【这个物】的编号,不是内容。**
这句话很重要:**软件里的对象,可以完全没有现实原型。**
比如 `Session`(会话)、`Transaction`(事务)、`Mutex`(互斥锁)——
**现实世界里你指不出它们是哪个【物】。**
实测:状态 {'n': 2}、行为 ['add']、标识是一个六位数。
这三样凑齐就是对象——不需要它在现实里对应任何东西。
30 秒实验
- 给你的一个类做一次三要素检查,看缺不缺。
- 想一想:只有状态没有行为的类,算不算对象。
常见误区
- 以为对象必须有现实原型。Session、Mutex 都没有。
- 以为
id()是内容摘要。它只是身份编号,每次运行都变。
自测三问(点开对答案)
1. 对象的三要素是什么? —— 状态(实例变量)、行为(方法)、标识(内存地址)。 2. 怎么在代码里验这三样? —— 状态看 vars(),行为看 dir() 里能调用的成员,标识看 id()。 3. 没有现实原型的对象存在吗? —— 存在而且是必需的:Session、Transaction、Mutex、缓存、连接池都是。- ☐ 下一节(2.8 软件并不会直接表示现实世界)
相关笔记
- 面向对象和现实世界是似是而非的 — 为什么必须拆掉比喻
- 软件并不会直接表示现实世界 — 定义之后看现实
- 每次创建实例都会使用堆区 — 标识在内存里是什么
- 🧩 面向对象是怎样工作的 · 索引