最近瀏覽一個專案的程式碼時發現,其中有一些以前提交的編碼,定義的一些類(不管他是POJO、DTO、DAO、PO、BO、VO、QO、ENTITY還是就是個物件,不重要。總之就是資料傳輸物件。),這些類他的寫法非常的瀟灑,屬性全部是public的,沒有任何的get、set方法,看起來非常乾淨、整潔。比如基本都像下面這樣
public class InventoryVo {
@JSONField(name = "pk")
public String id;
@JSONField(name = "itemName")
public String name;
public Integer quantity;
public String skuCode;
public String pid;
}
//使用的地方也非常的方便
//由其他層獲取到資料tradeDetail,這裡的業務層接入,然後轉為了json,準備請求一個http介面.
EastInventoryVo inventoryVo = new EastInventoryVo();
inventoryVo.id="10000000001";
inventoryVo.name=tradeDetail.getProductName();
inventoryVo.quantity=tradeDetail.getQuan();
inventoryVo.skuCode=tradeDetail.getProductCode();
inventoryVo.setLocator(tradeDetail.getFormArea();
String jsonString = JSON.toJSONString(inventoryVo);
公開所有成員給外部直接訪問
從業務邏輯上來看,他實現了功能,沒有任何問題,看起來程式碼乾淨,絲滑,甚至我都覺得大家總是寫get、set,總是搞什麼封裝,搞什麼物件導向有點多餘了,這讓我蠢蠢欲動啊 wa哇哇,必須來一下。難道我們寫那麼多的私有成員,然後共有方法訪問,多此一舉嗎??
那句話叫什麼,一時編碼一時爽,一直編碼一直爽。不。。跑錯片場了。這裡真的是一時編碼一時爽,維護就是火葬場
從編碼規範來說
- 沒什麼多說的,無論是公司內部現行的java程式設計規範,還是阿里的泰山版、嵩山班當中都未有明確說明不許這樣。 所以說他做的很好,對吧,啊對對是的!快速完成任務,你就說屌不屌吧。
然後陷入思考,為什麼,為什麼沒有加入這一條,是因為預設大家都懂OOP,都知道面向程式設計的基本特點,和基本原則吧。 - 但是,如果我們使用一些靜態程式碼分析工具絕逼會全部提示出來。(如 SonarQube、Checkstyle)會檢測未封裝的欄位,並提示開發者進行改進。
- 其實很多業內的編碼規範(如 Google Java Style Guide、Oracle Java Coding Conventions)都強調封裝的重要性,將其作為編寫高質量程式碼的標準之一。
- 這歷史程式碼沒有做程式碼審查嗎,是的,太那個了,我還沒來不知道呢
從OOP的角度來看
- 我們天天喊著封裝、繼承、多型,為什麼要封裝起來,成員全部裸奔、全部對外開放,多方便,很符合開放精神的。從眼前實現功能上來看,確實很快,但稍微想一下就會發現這種裸奔的現象,不受控制的玩法,就好比去大街道上給所有人展示自己,誰也可以來訪問,隨時隨地上手
- 破壞內聚性,封裝確保類的內部狀態和行為是內聚的,不會因為外部的變化而頻繁修改。如果這樣的類被到處使用,後續想擴充套件和對成員變數做控制就得將使用它的地方全部修改,而無法再此類的內部完成,也就是說同時破壞了單一職責原則、開閉原則
- 在《Java程式設計思想》和《Effective Java》中都有專門對類成員的可訪問性做闡述,指出不使用封裝所帶來的弊端“要在公有類而非公有域中使用訪問方法”、“使可變性最小化”。簡單說就是這種類,如果是確定在內部某一個地方使用,類本身不是公有的情況下,而且本來就一個地方使用,那麼採用公有的形式定義全部開放是可以的,但絕大多數情況下應該遵循封裝的原則,保證類的內部狀態。
比如上面的InventoryVo 這個類,我現在要對quantity做一個簡單的正數判斷,如何處理,在每一個呼叫它的地方去加一個判斷嗎?如果要想在類的內部實現呢,你會發現開始難受了,大家都直接訪問的成員變數,又沒對外提供方法,還沒法處理了
在比如我現在有個需求,基本欄位和InventoryVo一樣,只是新增幾個自己的欄位,同時又不想展示skuCode、pid這些欄位,如何實現??
//繼承了InventoryVo,新增了自己的擴充套件,但是沒法覆蓋父類的公有欄位,更不可能覆蓋訪問修飾符,像上面說的,要遮蔽掉幾個欄位,發現沒辦法遮蔽。總之要基於InventoryVo來做擴充套件變得困難。
//有人會說那你子類也定義個 String skuCode等欄位,然後提供方法,控制訪問不就行了?寶,那樣不行,JSL已經規定子類覆蓋不了父類的公有欄位,你只能透過super去訪問。而如果你也同樣在子類定義成public的,當你是用 父類() x = new 子類() 的形式定義物件,然後用x.skuCode你猜使用的誰的?那如果在改成 子類() x = new 子類(),又使用的誰的?
public class EastInventoryVo extends InventoryVo {
private String locator;
private String batchNo;
public String getLocator() {
return this.locator;
}
public void setLocator(String locator) {
this.locator = locator;
}
public String getBatchNo() {
return this.batchNo;
}
public void setBatchNo(String batchNo) {
this.batchNo = batchNo;
}
}
從java語言規範(JLS)來看
- 宣告為 private 的類的成員不會被該類的子類繼承。
- 只有宣告受保護或公共的類的成員才能由宣告該類的包以外的包中宣告的子類繼承。
- 建構函式、靜態初始值設定項和例項初始值設定項不是成員,因此不能繼承。
- 類的欄位、方法、成員類和成員介面可以具有相同的名稱,因為它們在不同的上下文中使用,並且由不同的查詢過程消除歧義(6.5)。然而,作為一種風格,這是不被鼓勵的。
https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#d5e13645
PS
其實儘可能按照OOP的原則來實現編碼,可讀性、可擴充套件性、可維護性都會提高,就算是一個簡單的POJO類,如今的IDE都支援直接生成get/set方法、構造方法等,非常方便,必要的時候直接生成即可。當然也不是說所有的POJO類,都必須生成get/set方法,加入判斷等,而是依據實際情況來具體處理。