类属性公共还是私有

决定一个类的属性(成员变量)是公共(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 响应),可适当放宽

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

相关推荐
x***B4111 小时前
TypeScript项目引用
前端·javascript·typescript
●VON1 小时前
使用 Electron 构建天气桌面小工具:调用公开 API 实现跨平台实时天气查询V1.0.0
前端·javascript·electron·openharmony
心随雨下1 小时前
TypeScript泛型开发常见错误解析
java·开发语言·typescript
穷人小水滴2 小时前
使用 epub 在手机快乐阅读
javascript·deno·科幻
爱学习的程序媛4 小时前
《深入浅出Node.js》核心知识点梳理
javascript·node.js
Robet5 小时前
TS和JS成员变量修饰符
javascript·typescript
方法重载5 小时前
前端性能优化之“代码分割与懒加载”)
javascript
我叫张小白。5 小时前
Vue3 响应式数据:让数据拥有“生命力“
前端·javascript·vue.js·vue3
laocooon5238578865 小时前
vue3 本文实现了一个Vue3折叠面板组件
开发语言·前端·javascript