重構改善既有代碼設計--重構手法18:Self Encapsulate Field (自封裝字段)
你直接訪問一個值域(field),但與值域之間的耦合關系逐漸變得笨拙。
為這個值域建立取值/設值函數(getting/setting methods),並且只以這些函數來訪問值域。
private int _low, _high;
boolean includes(int arg) {
return arg >= _low && arg <= _high;
}
==〉
private int _low, _high;
boolean includes(int arg) {
return arg >= getLow() && arg <= getHigh();
int getLow() {return _low;}
int getHigh() {return _high;}
動機
如果你想訪問superclass中的一個值域,卻又想在subclass中將[對這個變量的訪問]改為一個計算後的值,這就是最該使用Self Encapsulate Field(171)的時候。[值域自我封裝]只是第一步。完成自我封裝之後,你可以在subclass中根據自己的需要隨意覆寫取值/設值函數(getting/setting methods)。
作法
1. 為[待封裝值域]建立取值/設值函數(getting/setting methods)。
2. 找出該值域的所有引用點,將它們全部替換為[對於取值/設值函數的調用]。
如果引用點是[讀值]值域值,就將它替換為[調用取值函數];如果引用點是[設定]值域值,就將它替換為[調用設值函數]。
你可以暫時為設值域改名,讓編譯器幫助你查找引用點。
3. 將該值域聲明為private。
4. 復查,確保找出所有引用點。
5. 編譯,測試。
class IntRange {
private int _low, _high;
boolean includes(int arg) {
return arg >= _low && arg <= _high;
}
void grow(int factor) {
}
IntRange(int low, int high) {
_low = low;
_high = high;
}
}
為了封裝_low和_high這兩個值域,我先定義[取值/設值函數](如果此前沒有定義的話),並使用它們:
class IntRange {
private int _low, _high;
boolean includes(int arg) {
return arg >= _low && arg <= _high;
}
void grow(int factor) {
SetHigh(getHigh()*factor);
}
int getLow() {
return _low;
}
int getHigh() {
return _high;
}
void setLow(int arg) {
_low = arg;
}
void setHigh(int arg) {
_high = arg;
}
}
使用本項重構時,一般說來,設值函數被認為應該在[對象創建後]才使用,所以初始化過程中的行為有可能與設值函數的行為不同。這種情況下,我允許在構造函數中直接訪問值域,要不就是建立另一個獨立的初始化函數:
IntRange(int low, int high) {
initialize(low, high);
}
private void initialize(int low, int high) {
_low = low;
_high = high;
}
一旦你擁有一個subclass,上述所有動作的價值就體現出來了。如下所示:
class CappedRange extends IntRange {
CappedRange(int low, int high, int cap) {
super(low, high);
_cap = cap;
}
private int _cap;
int getCap() {
return _cap;
}
int getHigh() {
return Math.min(super.getHigh(), getCap());
}
}
現在,我可以CappedRange class中覆寫getHigh(),從而加入對cap的考慮,而不必修改IntRange class的任何行為。
重構改善既有代碼設計--重構手法18:Self Encapsulate Field (自封裝字段)