目錄
- 1、Replace Param with Query
- 2、Preserve Whole Object
- 3、Introduce Param Object
- 4、Remove Flag Argument
- 5、Combine Functions into Class
- Reference
當我們需要在超長函數中提煉子函數時,如果函數內有大量的參數和臨時變量,這將會對函數的提煉形成很大阻礙,你可能會形成過長參數列表,當然你也可以使用全局變量,顯然上面兩種都不是很好的辦法。
函數參數列表應該總結出函數的可變性,標志出函數可能體現出行為差異的主要方式。參數列表應當避免重復以及過長。
《重構》給我們提供了幾個好點子:(WHAT)
1、以查詢取代參數
2、保持對象完整
3、引入參數對象
4、移除標記參數
5、函數組合成類
接下來講解它們具體怎么做(HOW)
1、Replace Param with Query
注意點:若移除參數可能會給函數體增加不必要的依賴關系,不要使用。
具體做法:使用提煉函數將參數的計算過程提煉到一個獨立的函數
class Order {
/*
...
*/
double getFinalPrice()
{const double basePrice = this.quantity * this.itemPrice;int discountLevel = 1;if (this.quantity > 100) discountLevel = 2;return this.discountedPrice(basePrice, discountLevel);
}double discountedPrice(const double basePrice, int discountLevel)
{switch (discountLevel) {case 1: return basePrice * 0.95;case 2: return basePrice * 0.9;}
}};
修改后的代碼:
class Order {
public:int quantity = 0;int itemPrice = 0;int discountLevel = 0;Order(int quan, int price, int level){quantity = quan;itemPrice = price;discountLevel = level;}double getFinalPrice(){const double basePrice = this->quantity * this->itemPrice;return this->discountedPrice(basePrice);}int getDiscountLevel(){return (this->quantity > 100) ? 2 : 1;}double discountedPrice(const double basePrice){switch (this->getDiscountLevel()) {case 1: return basePrice * 0.95;case 2: return basePrice * 0.9;}}};
當需要使用discountLevel 變量的時候,后者自己調用函數,不需要把結果傳入了。
debug結果正確:
int main() {Order* order = new Order(1000,1,0);double res = order->getFinalPrice();std::cout << res << std::endl;
}
2、Preserve Whole Object
需要注意:
如果從一個對象中抽取幾個值,單獨對這幾個值做某些邏輯操作,通常標志著這段邏輯應該被搬移到對象中,然后從外部調用它。
當看見代碼從一個記錄結構中導出幾個值,然后又把這幾個之一起傳給一個函數,那么最好把整個記錄傳給這個函數,在函數體內部導出所需要的值。
舉例:一個室溫監控系統,負責記錄一天中最高氣溫和最低氣溫,然后將實際溫度范圍與預定溫度控制計劃比較,如果不符合要求,發出警告。
class HeatingPlan {
public:bool withinRange(int bottom, int top){return (bottom >= this._tempRange.low) && (top <= this._tempRange.high);}
};
int main() {// 調用方const int low = aRoom.daysTempRange.low;const int high = aRoom.daysTempRange.high;if(aPlan.withinRange(low, high))std:: cout << "Warning" << std::endl;
}
我們不必將溫度范圍的信息拆開單獨傳遞,只需要將整個范圍對象傳遞給withinRange即可
通過重構,可以寫成下面形式:
class HeatingPlan {
public:bool withinRange(int bottom, int top){return (bottom >= this._tempRange.low) && (top <= this._tempRange.high);}bool xxNewWithinRange(tempRange range){const int low = range.low;const int high = range.high;const bool res = this.withinRange(low, high);return res;}
};
int main() {// 調用方const tempRange range = aRoom.daysTempRange;const bool isWithinRange = aPlan.xxNewWithinRange(range);if(isWithinRange)std:: cout << "Warning" << std::endl;
}
3、Introduce Param Object
當一組數據項總是結伴而行,出沒于一個又一個函數,這樣的一組數據稱為數據泥團,通常用一個數據結構來代替它們。
如下方,查看一組溫度讀數是否有超出運行范圍。
bool readingsOutsideRange(Station station, int min, int max)
{return station.readings.filter(station.temp < min || station.temp > max);
}
int main() {// 調用方bool res = readingsOutsideRange(station, operatingPlan.temperatureFloor,operatingPlan.temperatureCeiling);
}
很顯然,temperatureFloor與temperatureCeiling是一堆數據泥團。
我們構造一個新的類去聚合它們,并將判斷邏輯up到這個類的內部。
class NumberRange {
public:int min;int max;NumberRange(int min, int max){this->min = min;this->max = max;}bool contains(int argv) {return (argv >= this->min) && (argv <= this->max);}
};
bool readingsOutsideRange(Station station, NumberRange range)
{return station.readings.filter(!range.contains(station.temp));
}
int main() {// 調用方const NumberRange range = new NumberRange(operatingPlan.temperatureFloor, operatingPlan.temperatureCeiling);bool res = readingsOutsideRange(station, range);
}
4、Remove Flag Argument
以明確的函數取代參數
如:
function setDimension(name, value)
{if (name == "height")this->_height = value;else if(name == "width")this->_width = value;return;
}
應當轉換為
====>
function setHeight(value) {this->_height = value;}
function setWidth(value) {this->_width = value;}
標記參數指的是調用者用它來指示被調函數應該執行哪一部分的邏輯。
上面的情況還算簡單,可以重新構造兩個函數以及內部邏輯,但有時想將標記參數的分發邏輯剝離到頂層,需要的工作量很大:
我們可以退而求其次,保留原有的函數,在其之上添加兩個函數:
function setHeight(value) {return setDimension("height", value);}
function setWidth(value) {return setDimension("width", value);}
這兩個包裝函數分別代表了原函數的一部分使用方式,不過并非從原函數拆分,而是使用代碼文本強行定義的。
5、Combine Functions into Class
function base(aReading) {...}
function taxable(aReading) {...}
function calBaseCharge(aReading) {...}應當改為
======>
class Reading {base() {...}taxable() {...}calBaseCharge() {...}
};
如果發現一組函數形影不離地操作同一塊數據,且通常使用參數傳遞的方式將數據塊傳給函數,那么,是時候組建一個類。
類能夠明確地給這些函數提供一個共用地環境,在對象內部調用函數可以減少參數傳遞
Reference
《重構 改善既有代碼的設計.第二版》P324 P319 P140 P314 P144