模塊設計是否良好,有個重要的因素在於,相對外部模塊是否隱藏內部數據以及實現細節。
設計良好的模塊會隱藏實現細節,並將API與其實現隔離開來。
模塊之間通過API進行通信,對於內部工作情況互不可見。
即,封裝(encapsulation)——軟件設計的基本原則之一。
為什麼要封裝?
通過封裝可以有效地接觸各個模塊之間的耦合關系,使這些模塊可以獨立地開發、測試、優化、使用、理解和修改。
即:
(PS:回想我自己以前用php寫網站時根本沒有這些概念也不影響開發工作。當時覺得沒什麼,因為是我一個人寫的,再加上後來也沒有再擴展,於是沒有了種種體會。)
可以通過訪問控制保證封裝。
一個實體的可訪問性是通過實體聲明所在的位置和訪問修飾符共同決定的。
建議盡可能地使每個類或者成員不被外界訪問。
對於頂層的類和接口只有兩種訪問級別:包級私有和公有。
更小的可訪問性代表更小的兼容性,在以後發行的版本中可以放心對其進行修改。
如果是公有的,則需要一直考慮客戶的行為。(PS:如果一個頂層類只是在某一個類的內部被用到,則可以聲明為私有靜態內部類。)
比如這樣一個類,這樣做在以後的版本中便無法改變表示方式,而且無法加入任何約束:
class Point {
public double x;
public double y;
}
於是正如很多人的習慣那樣,對於公有類用公有的訪問方法替代公有field以保證數據在類內部的靈活性: class Point { private double x; private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}
(PS:當然,有些常見的類並沒有遵守這一規則比如java.awt.Point、java.awt.Dimension。
但原作者也明確指出這些類不值得效仿。)
如果一個field只能是公有的,且允許將其聲明為final,則危害能少一些。
我們仍然可以通過公有的訪問方法訪問該field。
但不能用公有的setter,而是通過構造器加入約束:
public final class Time {
private static final int HOURS_PER_DAY = 24;
private static final int MINUTES_PER_HOUR = 60;
public final int hour;
public final int minute;
public Time(int hour, int minute) {
if (hour < 0 || hour >= HOURS_PER_DAY)
throw new IllegalArgumentException("Hour: " + hour);
if (minute < 0 || minute >= MINUTES_PER_HOUR)
throw new IllegalArgumentException("Min: " + minute);
this.hour = hour;
this.minute = minute;
}
// Remainder omitted
}
對於成員(field,method,嵌套類,嵌套接口)有4種訪問級別:
當設計了類的公有API後,我們將所有成員都變成私有。
然後發現同一個包內的另一個類也需要訪問這個成員,於是便修改訪問級別。如果這種事情經常發生則應該檢查設計是否合理。
雖然package和private級別的成員都是類實現的一部分,不會影響導出的API。
但,該類實現了Serializable時則另當別論。
如果使用protected修飾,則需要注意,該成員時導出的API的一部分。
另外,實例field盡量不要設置為公有。如果一個field不是final或者是一個指向可變對象的final field,公有的訪問級別會破壞該field的不可變性,它已不是一個內部的數據表示,而且非線程安全。
當然,這對於長度!=0的數組也是一樣的。
如果需要將某數組聲明為公有,可以嘗試以下方式:
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
或者可以使用clone,每次都拷貝一個數組:
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
return PRIVATE_VALUES.clone();
}