差别
这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
| csharp:类:嵌套类 [2025/11/17 09:49] – [1. 嵌套类是外部类的组成部分] 张叶安 | csharp:类:嵌套类 [2025/11/27 10:21] (当前版本) – [1. 内部类需要被多处复用] 张叶安 | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| - | # 一、什么时候应该用嵌套类(C# 示例) | + | ====== |
| - | ## 1. 嵌套类是外部类的组成部分 | + | |
| - | 例如 `Person.Address` 很自然地属于 Person。 | + | |
| - | ```csharp | + | 嵌套类(Nested Class)是在另一个类内部声明的类。在 C# 中,嵌套类与外部类的关系主要是**访问控制**的关系,而不是对象实例的关系。 |
| + | |||
| + | |||
| + | **C# 与 Java 的区别**: | ||
| + | 在 C# 中,嵌套类默认**不会**自动持有外部类实例的引用(类似于 Java 的 static inner class)。如果需要访问外部类的非静态成员,必须显式传递外部类的实例。 | ||
| + | |||
| + | ===== 一、什么时候应该用嵌套类 (最佳实践) ===== | ||
| + | |||
| + | 嵌套类的核心使用原则是:**高内聚,低耦合**。当一个类仅仅服务于另一个类时,嵌套是最好的选择。 | ||
| + | |||
| + | ==== 1. 逻辑上的组成部分 (强组合关系) ==== | ||
| + | |||
| + | 当子对象离开父对象就没有独立存在的意义,或者其定义仅在父对象上下文中有效时。 | ||
| + | |||
| + | < | ||
| public class Person | public class Person | ||
| { | { | ||
| 行 15: | 行 27: | ||
| } | } | ||
| - | | + | // 嵌套类:Address 被视为 Person 的一部分 |
| + | public class Address | ||
| { | { | ||
| public string City { get; } | public string City { get; } | ||
| 行 27: | 行 40: | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | 使用理由 | + | **核心原理**: |
| - | - | + | |
| - | | + | |
| - | - 隐藏实现细节 | + | |
| - | --- | + | ==== 2. 辅助器模式 (Builder / Iterator) ==== |
| - | ## 2. 嵌套类作为外部类的辅助器(例如 builder | + | 这是嵌套类最经典的使用场景。Builder(构建者)或 Iterator(迭代器)通常需要访问外部类的私有构造函数或私有数据。 |
| - | ```csharp | + | < |
| public class User | public class User | ||
| { | { | ||
| 行 44: | 行 56: | ||
| public int Age { get; } | public int Age { get; } | ||
| + | // 私有构造函数:强制外部必须通过 Builder 来创建实例 | ||
| private User(string name, int age) | private User(string name, int age) | ||
| { | { | ||
| 行 58: | 行 71: | ||
| { | { | ||
| _name = name; | _name = name; | ||
| - | return this; | + | return this; // 链式调用 |
| } | } | ||
| 行 69: | 行 82: | ||
| public User Build() | public User Build() | ||
| { | { | ||
| + | // 嵌套类可以访问外部类的 private 构造函数 | ||
| return new User(_name, _age); | return new User(_name, _age); | ||
| } | } | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | 使用理由 | + | **核心原理**: |
| - | - Builder 的存在意义仅服务 User | + | * **访问权限**:嵌套类拥有访问外部类 `private` 成员的特权。 |
| - | - 避免污染全局命名空间 | + | * **单一职责**:`Builder` 的唯一职责就是构建 `User`,放在外部没有意义。 |
| - | --- | + | ==== 3. 需要访问外部类的私有成员 ==== |
| - | ## 3. 非静态嵌套类需要访问外部类成员 | + | 当一个辅助类需要操作主类的内部状态(私有字段),但又不想把这些字段公开(public)给全世界时。 |
| - | (C# 的嵌套类默认 *不* 自动持有外部类实例,需要你传入) | + | |
| - | ```csharp | + | < |
| public class Machine | public class Machine | ||
| { | { | ||
| - | private int _state = 0; | + | private int _state = 0; // 私有状态 |
| public class Handler | public class Handler | ||
| 行 93: | 行 106: | ||
| private readonly Machine _machine; | private readonly Machine _machine; | ||
| + | // C# 嵌套类不自动持有外部引用,需要手动传入 | ||
| public Handler(Machine machine) | public Handler(Machine machine) | ||
| { | { | ||
| 行 100: | 行 114: | ||
| public void Increase() | public void Increase() | ||
| { | { | ||
| + | // 关键点:可以直接访问外部类的 private 字段 _state | ||
| _machine._state++; | _machine._state++; | ||
| } | } | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | 使用理由 | + | **核心原理**: |
| - | - Handler 的功能依赖 | + | * **白盒操作**:`Handler` 是 `Machine` |
| - | - 嵌套使结构更清晰 | + | |
| - | --- | + | ===== 二、什么时候不应该用嵌套类 (反模式) ===== |
| - | # 二、什么时候不应该用嵌套类(C# 示例) | + | 滥用嵌套类会导致代码难以阅读、难以测试,甚至引发内存问题。 |
| - | ## 1. 内部类可能被多个外部模块复用 | + | ==== 1. 内部类需要被多处复用 |
| - | 这会导致奇怪的层级结构。 | + | |
| - | ```csharp | + | 如果一个类不仅被外部类使用,还被其他模块使用,它就不应该被嵌套。 |
| + | |||
| + | < | ||
| public class Order | public class Order | ||
| { | { | ||
| - | public class MathUtils | + | |
| + | | ||
| { | { | ||
| public static int Add(int a, int b) => a + b; | public static int Add(int a, int b) => a + b; | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| + | |||
| + | |||
| + | **后果**:其他类如果要用这个工具,必须写成 | ||
| - | 错误原因 | ||
| - | - MathUtils 与 Order 毫无关系 | ||
| - | - 应该独立成为工具类 | ||
| - | --- | + | ==== 2. 生命周期管理风险 (内存泄漏) ==== |
| - | ## 2. 嵌套类生命周期与外部类不一致 | + | 如果嵌套类的实例生命周期比外部类长,且嵌套类持有了外部类的引用,会导致外部类无法被垃圾回收(GC)。 |
| - | 例如:后台任务会比外部类活得更久。 | + | |
| - | ```csharp | + | < |
| public class Controller | public class Controller | ||
| { | { | ||
| - | | + | // 假设这是一个很大的对象,占用大量内存 |
| + | private byte[] _largeData = new byte[1024 * 1024]; | ||
| + | |||
| + | public class BackgroundWorker | ||
| { | { | ||
| - | // 可能会导致持有外部类引用过久(如果你传进来) | + | |
| + | |||
| + | public void Run() { | ||
| + | | ||
| + | while(true) { /* ... */ } | ||
| + | } | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </code> |
| - | + | ||
| - | 如果 BackgroundWorker 长期运行,而你在其中引用了 Controller,就会阻止 Controller 被回收。 | + | |
| - | + | ||
| - | 解决方式 | + | |
| - | - 不把长期对象设计为嵌套类 | + | |
| - | - 或使用静态/独立类 + 弱引用 | + | |
| - | --- | + | **核心原理**: |
| + | * 如果 `BackgroundWorker` 被单独传递给一个线程池运行,而它又引用了 `Controller`,那么只要任务没结束,巨大的 `Controller` 对象就永远无法释放。 | ||
| + | * **建议**:对于长期运行的后台任务,尽量使用独立的类,或者确保不持有外部类的强引用(使用 `WeakReference`)。 | ||
| - | ## 3. 嵌套层级太深影响可读性 | + | ==== 3. 嵌套层级过深 ==== |
| - | ```csharp | + | < |
| public class A | public class A | ||
| { | { | ||
| 行 163: | 行 182: | ||
| public class C | public class C | ||
| { | { | ||
| - | public class D { } | + | public class D { } // A.B.C.D |
| } | } | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | 没有明确业务意义时应避免这种结构。 | + | **核心原理**: |
| + | * **可读性灾难**:这种代码被称为“俄罗斯套娃”代码,极难阅读和维护。 | ||
| + | * **重构困难**:如果将来要把 `D` 移出来,所有引用它的代码都需要修改。 | ||
| - | --- | + | ==== 4. 逻辑关系松散 ==== |
| - | ## 4. 内部类与外部类逻辑关系不强 | + | 仅仅为了“整理代码”而把不相关的类塞进去是不对的。 |
| - | 例如: | + | |
| - | ```csharp | + | < |
| public class Company | public class Company | ||
| { | { | ||
| - | public class Logger | + | |
| + | | ||
| { | { | ||
| public void Log(string message) => Console.WriteLine(message); | public void Log(string message) => Console.WriteLine(message); | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | + | ||
| - | Logger 并不是 Company 的组成部分,应该是独立工具类。 | + | |
| - | + | ||
| - | --- | + | |
| - | + | ||
| - | # 总结(C# 场景) | + | |
| - | + | ||
| - | 应该用嵌套类当: | + | |
| - | - 逻辑上只属于外部类 | + | |
| - | - 属于外部类的组成部分 | + | |
| - | - 辅助外部类工作 | + | |
| - | - 更强的封装性和结构清晰度 | + | |
| - | + | ||
| - | 不应该用嵌套类当: | + | |
| - | - 类具有可复用性 | + | |
| - | - 生命周期与外部类不一致 | + | |
| - | - 业务上没有强逻辑归属 | + | |
| - | - 过深层级影响可读性 | + | |
| - | --- | + | **核心原理**: |
| + | * **单一职责原则 (SRP)**:`Company` 应该只负责公司的业务逻辑,不应该负责定义日志工具。`Logger` 应该是一个独立的类或接口。 | ||
| + | ===== 总结 ===== | ||
| + | ^ 维度 ^ 建议嵌套 ^ 建议独立 ^ | ||
| + | | **复用性** | 仅被外部类使用 | 被多个模块使用 | | ||
| + | | **访问权限** | 需要访问外部类 private 成员 | 仅访问 public 成员 | | ||
| + | | **生命周期** | 与外部类共存亡 | 独立于外部类存在 (如后台任务) | | ||
| + | | **逻辑关系** | 是外部类的一部分 (Is-Part-Of) | 与外部类无关 (工具类/ | ||