首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

冒號和他的學生們(連載 24)——對象封裝:從廣義打包到狹義信息隱藏的深度解析

     編輯:Java 相關知識

第 24 課以幽默風格切入,層層遞進剖析封裝的三個層次:廣義封裝(打包+語法糖)、狹義封裝(訪問控制+信息隱藏)、語義封裝(防御性拷貝、接口語義設計)。通過 User 類反例,揭示可變對象引用外洩、boolean 語義不足、方法命名洩露實現等經典陷阱。

封裝三個層次

層次內容效果
廣義封裝數據與方法打包成類(模塊機制)相關邏輯聚合、語法糖(queue.add(item) vs queue_add(queue,item))、名字空間隔離
狹義封裝打包 + 訪問控制(黑盒)信息隱藏:抽象接口與實現分離、用戶無權訪問底層
語義封裝超越語法的契約設計防御性拷貝、不可變對象、接口語義不洩露實現細節

訪問控制的局限與繞過

  • C++:指針運算可間接訪問 private
  • Java:反射 setAccessible(true) 可強行訪問私有字段/方法
  • 屬於 Hack 手段,特殊用途外不應使用

經典反例:User 類的四個方法全違背信息隱藏

class User {
    private Date birthday;  // 可變對象
    private boolean sex;    // 語義不足

    public Date getBirthday() { return birthday; }        // 1. 可變引用外洩
    public void setBirthday(Date d) { this.birthday = d; } // 2. 別名問題

    public boolean getSex() { return sex; }               // 3. boolean 語義不足
    public void setSex(boolean sex) { this.sex = sex; }

    public int computeAge() { ... }                       // 4. 命名洩露實現(計算 vs 獲取)
}

問題逐一拆解

  1. getBirthday 返回 Date 引用:客戶可竄改內部狀態。
    修正:防御性拷貝 return new Date(birthday.getTime()); 或用不可變類型(如 LocalDate)。
  2. setBirthday 別名問題:傳入對象若被外部持有並修改,同樣影響內部狀態。
    修正:同樣防御性拷貝存入。
  3. boolean sex 語義不足:無法表達「未知」、「保密」、「非二元」等狀態。
    修正:用 Boolean(三態)、char、String 或雙方法 isMale()/isFemale()。
  4. computeAge 命名洩露實現:暗示年齡是現場計算。若改為存儲年齡倒推生日,方法名需改 computeBirthday?
    修正:統一命名 getAge(),隱藏實現策略(計算/存儲/緩存)。

關鍵設計原則

  • 可變 + 引用 = 風險:基本類型、String、不可變對象、C++ const 引用、C# 結構體 均安全。
  • 普通不等於正確:常見做法往往不合格,合格程序員是不普通的。
  • 信息隱藏非絕對:純數據載體(DTO/VO)可開放字段;性能熱點經權衡可妥協,但須文檔化。
  • 接口語義 > 實現細節:命名反映「是什麼」而非「怎麼做」。

課後作業

重構 User 類:應用防御性拷貝、豐富性別語義、修正方法命名、添加必要構造器與不可變性保證。

本章金句

「通,則大處圓融合一而小處各具其妙;不通,則大處千變萬化而小處無所分別。」「封裝的故事遠未結束,下節課繼續。」
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved