类属性公共还是私有

决定一个类的属性(成员变量)是公共(public)还是私有(private) ,是面向对象设计中的核心问题之一。这不仅关乎代码封装性,还直接影响系统的可维护性、可扩展性和健壮性

以下是系统化的思考框架和实用原则,帮助你做出合理决策:


🔑 核心原则:最小暴露原则(Principle of Least Exposure)

"只暴露必须暴露的内容,其余一律隐藏。"

换句话说:

默认私有 ,只有当确实需要外部访问时,才设为公共。


一、判断标准:问自己这几个问题

1. 外部是否需要直接读取这个值?

  • ✅ 是 → 考虑 public 或提供 getter
  • ❌ 否 → private

📌 示例:

typescript 复制代码
class BankAccount {
  private balance: number; // 外部不应直接读余额(需鉴权/日志)
  getBalance(): number { /* ... */ } // 通过方法控制访问
}

2. 外部是否需要直接修改这个值?

  • ✅ 是 → 考虑 public 或提供 setter(但要谨慎!)
  • ❌ 否 → private

⚠️ 直接暴露可变状态容易导致 bug:

ini 复制代码
// 危险!
user.profile.settings.darkMode = true; // 绕过校验/事件通知

更好的方式:

perl 复制代码
user.setTheme('dark'); // 内部可触发 re-render / save / log

3. 这个属性是否属于"内部实现细节"?

  • 如果未来可能重构、重命名或删除 它 → 必须私有
  • 如果它是稳定契约的一部分(如 API 返回结构)→ 可 public

💡 例子:

  • 缓存字段(private cache: Map<...>)→ 私有
  • 用户 ID(public id: string)→ 公共(业务标识)

4. 是否需要保持对象的"不变性"(Invariants)?

如果属性参与维持对象的内部一致性,则必须私有,并通过方法控制变更。

📌 示例:矩形的宽高不能为负数

typescript 复制代码
class Rectangle {
  private _width: number;
  private _height: number;

  setWidth(w: number) {
    if (w < 0) throw new Error('Width must be positive');
    this._width = w;
  }
}

二、优先使用 方法(Method)而非公共属性

即使需要"读取"或"设置",也优先提供方法而非直接暴露属性:

场景 推荐做法
读取计算值 getFullName() 而非 fullName(除非是简单数据)
设置需校验 setEmail(email) 而非 email = ...
触发副作用 activate() 而非 isActive = true

✅ 好处:

  • 未来可加日志、权限、缓存、事件通知等逻辑
  • 避免"属性被意外覆盖"导致状态不一致

三、特殊情况处理

✅ 可以公开的属性类型

类型 说明 示例
不可变数据 初始化后永不改变 public readonly id: string
纯数据载体(DTO/POJO) 仅用于传输,无行为 interface UserDTO { name: string; email: string }
配置对象 明确设计为可读写的配置 public config: RenderConfig(但建议用 getter/setter 封装)

❌ 应避免公开的属性

  • 内部状态(如 isLoading, retryCount
  • 依赖其他属性的派生值(如 fullName = firstName + lastName → 应用 getter)
  • 敏感数据(密码、token、余额)
  • 复杂对象引用(如 private domElement: HTMLElement

四、TypeScript / JavaScript 中的具体实践

方案 1:使用 # 私有字段(推荐,ES2022+)

kotlin 复制代码
class Timer {
  #startTime: number;
  #isRunning = false;

  start() {
    this.#startTime = Date.now();
    this.#isRunning = true;
  }

  get elapsed() {
    return this.#isRunning ? Date.now() - this.#startTime : 0;
  }
}

✅ 真正私有,运行时安全

方案 2:TypeScript private(仅开发时保护)

typescript 复制代码
class Logger {
  private logs: string[] = [];
  log(msg: string) { this.logs.push(msg); }
}

⚠️ 注意:编译后仍可被外部访问,仅防"手误"

方案 3:readonly + 公共(用于不可变数据)

typescript 复制代码
class Point {
  constructor(
    public readonly x: number,
    public readonly y: number
  ) {}
}

✅ 安全暴露,且不可修改


五、团队协作建议

  1. 约定优于配置:团队统一规则,如"所有状态属性默认私有"
  2. 代码审查重点 :检查是否有不必要的 public 属性
  3. 文档说明:对 public 属性明确其用途和约束

✅ 快速决策流程图

csharp 复制代码
这个属性需要被外部访问吗?
│
├─ 否 → private / #
│
└─ 是 → 
     ├─ 是否需要修改? 
     │   ├─ 是 → 提供 setter 方法(而非直接 public)
     │   └─ 否 → 
     │        ├─ 是否不可变? → public readonly
     │        └─ 是否计算值? → 提供 getter 方法
     │
     └─ 是否属于稳定数据契约? → 可 public(如 DTO)

🎯 总结:黄金法则

"属性代表状态,状态应受控。
暴露行为(方法),而非状态(属性)。"

  • 默认 私有private#
  • 仅在必要且安全时暴露为公共
  • 优先通过 方法 控制访问,而非直接暴露字段
  • 对于纯数据对象(如 API 响应),可适当放宽

这样做,你的类将更健壮、更易测试、更易演进。

相关推荐
এ慕ོ冬℘゜几秒前
jQuery attr() 方法超详细讲解:属性获取、赋值、实战踩坑全解
前端·javascript·jquery
啊啊啊迈 旋棍3 分钟前
【译】TypeScript 7 测试版已在 Visual Studio 2026 18.6 Insiders 3 中默认启用
ubuntu·typescript·visual studio
Hilaku1 小时前
为什么大厂对前端算法要求极高?
前端·javascript·程序员
你可如意1 小时前
从Promise开始,一篇让你彻底建立起JS的异步编程模型
javascript
用户84298142418102 小时前
AI时代,JS混淆加密还有用吗?
前端·javascript
申君健24864182772023 小时前
MCP TypeScript SDK v2 完整升级变化说明
javascript
Ivanqhz3 小时前
Rust Lazy浅析
java·javascript·rust
铁皮饭盒4 小时前
面试官:如何用 Bun + JS 实现安全的文件 MCP 工具集
前端·javascript·后端
weixin_BYSJ19876 小时前
springboot3家政平台小程序--附源码00904
java·javascript·spring boot·python·django·flask·php
Wect7 小时前
TypeScript 完全指南
前端·javascript·typescript