Files
PythonLearn/03_面向对象/3_2_Python与Java的封装差异/README.md

245 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 3-2 课Python 与 Java 的封装差异
## 一、本课定位
Java 通过 `public``protected``private` 等访问修饰符实施较严格的访问控制。Python 更信任开发者主要使用命名约定和属性Property表达接口边界。
本课不重复封装的一般理论,只讲 Java 开发者迁移到 Python 时必须调整的认识。
## 二、本课目标
完成本课后,你将能够:
1. 理解 Python 没有与 Java 完全等价的强制 `private`
2. 解释普通名称、单下划线和双下划线的区别;
3. 使用 `@property` 提供读取接口;
4. 使用 `@属性名.setter` 在赋值时执行校验;
5. 判断何时直接使用公开属性,何时需要 property
6. 避免把 Java getter/setter 机械翻译成 Python 方法。
## 三、前置知识
- Java 的访问修饰符、getter 和 setter
- Python 类、`self`、实例属性和实例方法;
- Python 异常处理。
## 四、差异速查表
| 意图 | Java 常见写法 | Python 常见写法 |
|---|---|---|
| 公开成员 | `public` | 普通名称,例如 `name` |
| 仅供内部使用 | `protected` 或包访问 | `_name` 命名约定 |
| 避免子类意外覆盖 | `private` | `__name` 触发名称改写 |
| 读取属性 | `getPrice()` | `product.price``@property` |
| 校验后赋值 | `setPrice(value)` | `product.price = value` 配合 setter |
| 强制安全边界 | 访问修饰符 | 下划线不能提供真正的安全隔离 |
## 五、单下划线:约定,不是限制
```python
self._owner = owner
```
`_owner` 表示“这是内部实现,外部代码通常不要直接依赖”。但是下面的代码仍然可以运行:
```python
print(account._owner)
```
因此,单下划线主要是团队沟通约定,不是运行时访问控制。
## 六、双下划线:名称改写,不是绝对私有
```python
self.__balance = 100.0
```
Python 会进行名称改写Name Mangling把属性名称处理成类似
```text
_Account__balance
```
主要目的之一是避免子类无意中使用相同名称覆盖父类属性。外部仍能通过改写后的名字访问它,所以它不是安全机制,也不能保护密码或密钥。
业务项目通常不要把所有内部属性都写成双下划线。只有确实需要避免子类名称冲突时再使用。
## 七、property保持属性语法同时执行方法逻辑
Java 常见写法:
```java
public double getBalance() {
return balance;
}
public void setBalance(double value) {
if (value < 0) {
throw new IllegalArgumentException();
}
balance = value;
}
```
Python 可以写成:
```python
@property
def balance(self) -> float:
return self.__balance
@balance.setter
def balance(self, value: float) -> None:
if value < 0:
raise ValueError("余额不能小于 0。")
self.__balance = value
```
调用方式仍像普通属性:
```python
print(account.balance)
account.balance = 200.0
```
读取时实际执行 getter 方法,赋值时实际执行 setter 方法。
## 八、不要为每个字段机械创建 property
如果属性只是公开保存数据,没有校验、计算或兼容需求,可以直接使用:
```python
self.name = name
```
不需要照搬 Java为它创建无逻辑的 `get_name()``set_name()`
适合使用 property 的情况包括:
- 赋值时需要校验;
- 返回值需要动态计算;
- 希望提供只读接口;
- 内部存储方式可能改变,但不希望调用方式变化。
## 九、只读 property
只定义 getter、不定义 setter
```python
@property
def owner(self) -> str:
return self._owner
```
调用者可以读取 `account.owner`,但执行 `account.owner = "其他人"` 时会报错。
需要注意:这仍然不是不可绕过的安全边界。调用者如果直接修改 `_owner`Python 不会阻止。它表达的是正常接口,不是对恶意代码的防护。
## 十、初始化时复用 setter
下面的写法可以让初始值和后续赋值使用相同校验:
```python
def __init__(self, balance: float) -> None:
self.__balance = 0.0
self.balance = balance
```
第二行 `self.balance = balance` 会调用 property setter。这样不会出现“对象创建时允许负数创建后却不允许负数”的规则不一致。
## 十一、完整示例
示例文件:
```text
03_面向对象/3_2_Python与Java的封装差异/encapsulation_comparison.py
```
运行命令:
```powershell
python .\03_面向对象\3_2_Python与Java的封装差异\encapsulation_comparison.py
```
示例输出会直接展示:
1. 单下划线属性仍能从外部访问;
2. property 使用属性语法执行校验逻辑;
3. 双下划线在对象中实际发生了名称改写;
4. 非法余额会被 setter 阻止。
## 十二、常见误区
### 12.1 认为 `_name` 等于 Java protected
它只是一种命名约定,不会限制外部或非子类代码访问。
### 12.2 认为 `__name` 能保护敏感信息
双下划线不是加密或权限控制。敏感信息仍需使用安全的配置、存储和权限机制。
### 12.3 为所有属性生成 getter 和 setter
没有额外逻辑时,直接访问公开属性更符合 Python 风格。需要保持属性语法并增加逻辑时再使用 property。
### 12.4 在 setter 内给 property 本身赋值
下面会无限递归:
```python
@balance.setter
def balance(self, value: float) -> None:
self.balance = value
```
setter 内应给实际存储属性赋值,例如 `self.__balance = value`
## 十三、课堂练习
打开:
```text
03_面向对象/3_2_Python与Java的封装差异/practice.py
```
完成 `Product` 类,练习内容包括:
1. 单下划线和双下划线属性;
2. 只读 `name` property
3. 可读写 `price` property
4. 价格与折扣率校验;
5. 使用 `try...except` 验证非法数据。
练习题中包含 Java 对照提醒和完整预期输出,可以直接从练习开始。
## 十四、参考答案
参考答案暂不写入练习文件。完成后,我会按照以下层级验证:
- 必须修复:语法错误、运行错误、业务结果错误、关键知识点使用错误;
- 建议改进:重复逻辑、容易产生误解的结构、明显影响可读性的写法;
- 可选优化:非必要类型注解、输出美化和不影响理解的风格统一。
只有“必须修复”的问题会阻止课程验收。
## 十五、本课小结
- Python 主要通过约定和接口设计实现封装;
- `_name` 表示内部使用,但外部仍能访问;
- `__name` 触发名称改写,主要用于避免子类名称冲突;
- 双下划线不是 Java `private` 的完全等价物,也不是安全机制;
- `@property` 让方法逻辑保留属性式调用体验;
- setter 可以集中完成赋值校验;
- 没有额外逻辑时,不必机械创建 getter 和 setter。
## 十六、验收标准
- 能解释单下划线和双下划线的区别;
- 能说明下划线为什么不能保护敏感信息;
- 能定义只读 property
- 能定义包含校验的 property setter
- 能避免 setter 自我赋值造成无限递归;
- 能用 property 统一初始化和后续赋值规则;
- `practice.py` 能正确阻止负数价格;
- 能说明 Python property 与 Java getter/setter 的调用差异。