高效重构 C++ 代码(中)
作者:魔术大师 发布时间:[ 2016/9/27 11:39:33 ] 推荐标签:测试开发技术 .NET
由于Shoes类的对象在其生命周期中type不会发生变化,所以可以为不同的类型码建立Shoes类型的子类,将不同type的计算行为放到相应子类中去,这样代码可以满足开放封闭性,以后再增加新类型的Shoes,只用独立地再增加一个子类,不会干扰到别的类型的计算。
于是我们决定使用重构手法Replace type code with subclasses(以子类取代类型码)来完成重构目标。(注意:倘若在Shoes的对象生命周期内type可以变化,不能使用该重构手法,而应该使用Replace type code with strategy/state(以策略或者状态模式取代类型码))。
以下是具体的重构过程,我们着重展示如何使用原子步骤以及锚点。
由于我们要以子类取代类型码type,所以首先使用Encapsulate field对type创建引用锚点,方便后面对type的替换。
Type Shoes::getType() const
{
return type;
}
double Shoes::getCharge(int quantity) const
{
double result = 0;
switch(getType())
{
case REGULAR:
result += price * quantity;
break;
case NEW_STYLE:
if (quantity > 1)
{
result += (price + (quantity - 1) * price * 0.8);
}
else
{
result += price;
}
break;
case LIMITED_EDITION:
result += price * quantity * 1.1;
break;
}
return result;
}
上面我们将getCharge中对type的直接使用替换为调用新创建的私有成员方法getType()。到后Shoes内只有构造函数中仍然直接使用type,接下来再处理构造函数。
为了屏蔽Shoes类型被子类化后对客户代码的影响,我们对Shoes创建工厂函数作为构造函数的引用锚点,用来对客户屏蔽不同种类Shoes的具体构造。首先执行原子步骤setup,创建出工厂函数。
struct Shoes
{
static Shoes* create(Type type, double price);
Shoes(Type type, double price);
double getCharge(int quantity) const;
private:
Type getType() const;
private:
Type type;
double price;
};
Shoes* Shoes::create(Type type, double price)
{
return new Shoes(type, price);
}
编译通过后,接下来执行原子步骤substitute。将原来客户代码直接调用Shoes构造函数的地方替换为调用工厂函数。替换完成后将Shoes的构造函数修改为protected(子类要用)。执行测试!
struct Shoes
{
static Shoes* create(Type type, double price);
double getCharge(int quantity) const;
protected:
Shoes(Type type, double price);
private:
Type getType() const;
private:
Type type;
double price;
};
// client code
// Shoes* shoes = new Shoes(REGULAR, 100.0);
Shoes* shoes = Shoes::create(REGULAR, 100.0);
为了简化,这里假设客户代码需要对创建出来的Shoes对象进行显示内存管理。
至此内外部锚点都已经创建OK。内部锚点getType在类内屏蔽对type的直接使用,方便后续以子类对type进行替换。外部锚点create向客户隐藏Shoes的具体构造,方便以子类的构造替换具体类型Shoes的构造。
接下来,我们逐一创建Shoes的子类,用于对Shoes中类型码的替换。
首先将Shoes内的getType函数修改为虚方法。然后执行原子步骤setup,创建类RegularShoes继承自Shoes,它覆写了getType方法,返回对应的类型码。
struct RegularShoes : Shoes
{
RegularShoes(double price);
private:
Type getType() const override;
};
RegularShoes::RegularShoes(double price)
: Shoes(REGULAR, price)
{
}
Type RegularShoes::getType() const
{
return REGULAR;
}
下面执行原子步骤substitute,用RegularShoes替换Shoes的构造函数中对于REGULAR类型的构造。
Shoes* Shoes::create(Type type, double price)
{
if(type == REGULAR) return new RegularShoes(price);
return new Shoes(type, price);
}
同样的方式创建NewStyleShoes和LimitedEditionShoes,并替换进工厂函数中。
NewStyleShoes::NewStyleShoes(double price)
: Shoes(NEW_STYLE, price)
{
}
Type NewStyleShoes::getType() const
{
return NEW_STYLE;
}
LimitedEditionShoes::LimitedEditionShoes(double price)
: Shoes(LIMITED_EDITION, price)
{
}
Type LimitedEditionShoes::getType() const
{
return LIMITED_EDITION;
}
Shoes* Shoes::create(Type type, double price)
{
switch(type)
{
case REGULAR:
return new RegularShoes(price);
case NEW_STYLE:
return new NewStyleShoes(price);
case LIMITED_EDITION:
return new LimitedEditionShoes(price);
}
return nullptr;
}
在Shoes::create方法中,类型都不匹配的情况下返回了nullptr,当然你也可以创建Shoes的一个NullObject在此返回。
至此,Shoes中不再需要类型码type了。为了安全的删除,我们执行原子步骤setup,先为Shoes添加一个无需type参数的构造函数Shoes(double price),然后执行原子步骤substitute,将子类中调用的Shoes(Type type, double price)全部替换掉。在这里为了避免子类间构造函数的重复,我们使用了C++11的继承构造函数特性。编译测试通过后,我们可以安全地将type和Shoes(Type type, double price)一起删除,同时将Shoes中的getType修改为纯虚函数,删除其在cpp文件中的函数实现。
struct Shoes
{
static Shoes* create(Type type, double price);
Shoes(double price);
virtual ~Shoes(){}
double getCharge(int quantity) const;
private:
virtual Type getType() const = 0;
private:
double price;
};
struct RegularShoes : Shoes
{
using Shoes::Shoes;
private:
Type getType() const override;
};
重构到现在,我们已经成功地用子类替换掉了类型码。但是这不是我们的目的,我们终希望能够把getCharge中的计算行为分解到对应子类中去。这是Replace Condition with Polymorphism(以多态取代条件表达式)。下面我们以原子步骤的方式完成它。
首先将getCharge声明为虚方法。然后使用原子步骤setup在子类中创建getCharge的覆写函数,将对应子类的计算部分copy过去。这里为了能够编译通过,需要将Shoes中的price成员变量修改为protected。
struct Shoes
{
static Shoes* create(Type type, double price);
Shoes(double price);
virtual ~Shoes(){}
virtual double getCharge(int quantity) const;
private:
virtual Type getType() const = 0;
protected:
double price;
};
struct RegularShoes : Shoes
{
using Shoes::Shoes;
private:
double getCharge(int quantity) const override;
Type getType() const override;
};
然后执行原子步骤substitute,用子类的getCharge对父类中的实现进行替换。这里只用删除Shoes::getCharge中对应REGULAR的分支。执行测试。
double Shoes::getCharge(int quantity) const
{
double result = 0;
switch(getType())
{
case NEW_STYLE:
if (quantity > 1)
{
result += (price + (quantity - 1) * price * 0.8);
}
else
{
result += price;
}
break;
case LIMITED_EDITION:
result += price * quantity * 1.1;
break;
}
return result;
}
同样的方式,将Shoes::getCharge中其余部分分别挪入到另外两个子类中,完成对Shoes::getCharge的替换,后删除Shoes::getCharge的实现部分,将其声明为纯虚函数。这时继承体系上的getType也不再需要了,一起删除。
struct Shoes
{
static Shoes* create(Type type, double price);
Shoes(double price);
virtual ~Shoes(){}
virtual double getCharge(int quantity) const = 0;
protected:
double price;
};
Shoes::Shoes(double price)
: price(price)
{
}
Shoes* Shoes::create(Type type, double price)
{
switch(type)
{
case REGULAR:
return new RegularShoes(price);
case NEW_STYLE:
return new NewStyleShoes(price);
case LIMITED_EDITION:
return new LimitedEditionShoes(price);
}
return nullptr;
}
struct RegularShoes : Shoes
{
using Shoes::Shoes;
private:
double getCharge(int quantity) const override;
};
double RegularShoes::getCharge(int quantity) const
{
return price * quantity;
}
struct NewStyleShoes : Shoes
{
using Shoes::Shoes;
private:
double getCharge(int quantity) const override;
};
double NewStyleShoes::getCharge(int quantity) const
{
double result = price;
if (quantity > 1)
{
result += ((quantity - 1) * price * 0.8);
}
return result;
}
struct LimitedEditionShoes : Shoes
{
using Shoes::Shoes;
private:
double getCharge(int quantity) const override;
};
double LimitedEditionShoes::getCharge(int quantity) const
{
return price * quantity * 1.1;
}
至此我们的重构目标已经达成。通过上例我们再次展示了如何使用原子步骤进行安全小步的代码修改,并且展示了锚点的使用。对于本例可能有人会说,getCharge中的switch-case被分解到每个子类中去了,但是我们在Shoes::create中又创建出了一个switch-case结构。但其实这两个switch-case背后的意义是不同的,重构后的仅仅是在做构造分发,职责清晰、简单明了; 如果后续再有根据类型执行不同算法的行为,直接实现在具体子类中,而不用增加新的switch-case结构了! 实际操作中,我们其实是在变化发生的时候先来进行上述重构,然后再增加新的代码。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南