当我们需要在超长函数中提炼子函数时,如果函数内有大量的参数和临时变量,这将会对函数的提炼形成很大阻碍,你可能会形成过长参数列表,当然你也可以使用全局变量,显然上面两种都不是很好的办法。
函数参数列表应该总结出函数的可变性,标志出函数可能体现出行为差异的主要方式。参数列表应当避免重复以及过长。
《重构》给我们提供了几个好点子:(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