重构大师(三)
用状态/策略替换类型代码
原文:
refactoringguru.cn/replace-type-code-with-state-strategy
什么是类型代码? 类型代码是指,当你不是使用单独的数据类型,而是有一组数字或字符串形成某个实体的允许值列表时。通常,这些特定的数字和字符串通过常量赋予可理解的名称,这就是为什么这种类型代码如此常见的原因。
问题
你有一个影响行为的编码类型,但你无法使用子类来消除它。
解决方案
用状态对象替换类型代码。如果有必要用类型代码替换字段值,可以“插入”另一个状态对象。
在此之前!用状态策略替换类型代码 - 之前之后!用状态策略替换类型代码 - 之后
为什么要重构
你有类型代码,它影响类的行为,因此我们不能使用用类替换类型代码。
类型代码影响类的行为,但由于现有的类层次结构或其他原因,我们无法为编码类型创建子类。这意味着我们不能应用用子类替换类型代码。
好处
-
这种重构技术是解决当具有编码类型的字段在对象生命周期中改变其值的情况的一种方法。在这种情况下,通过替换原始类所引用的状态对象来替换值。
-
如果需要添加一个编码类型的新值,你只需添加一个新的状态子类,而无需更改现有代码(参见开放/封闭原则)。
缺点
- 如果你有一个简单的类型代码案例,但仍然使用这种重构技术,你将会有许多多余(且不需要)的类。
好消息
这种重构技术的实现可以使用两种设计模式之一:状态或策略。无论选择哪种模式,实施方式都是一样的。那么在特定情况下你应该选择哪种模式呢?
如果你试图拆分控制算法选择的条件,请使用策略。
但如果编码类型的每个值不仅负责选择算法,还负责类的整体状态、字段值和许多其他操作,状态更适合这个工作。
如何重构
-
使用自我封装字段为包含类型代码的字段创建一个 getter。
-
创建一个新类,并赋予它一个适合类型代码目的的可理解名称。这个类将扮演状态(或策略)的角色。在其中,创建一个抽象的编码字段 getter。
-
为编码类型的每个值创建状态类的子类。在每个子类中,重定义编码字段的 getter,使其返回对应的编码类型值。
-
在抽象状态类中,创建一个接受编码类型值作为参数的静态工厂方法。根据该参数,工厂方法将创建各种状态的对象。为此,在其代码中创建一个大的条件;这将是重构完成时的唯一条件。
-
在原始类中,将编码字段的类型更改为状态类。在字段的 setter 中,调用工厂状态方法以获取新的状态对象。
-
现在你可以开始将字段和方法从超类移动到相应的状态子类中(使用 向下推送字段 和 向下推送方法)。
-
当所有可移动的对象都已被移动时,使用 替换条件语句为多态 来彻底摆脱使用类型代码的条件语句。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦阅读了吗?
不奇怪,阅读我们这里的所有文本需要 7 小时。
尝试我们关于重构的互动课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
用字段替换子类
问题
您有子类仅在其(返回常量的)方法上有所不同。
解决方案
在父类中用字段替换方法并删除子类。
之前之后
为什么重构
有时,重构正是避免类型代码的良方。
在某些情况下,子类层次结构可能仅在特定方法返回的值上有所不同。这些方法甚至不是计算的结果,而是严格在方法本身或方法返回的字段中设定的。为了简化类架构,可以将此层次结构压缩为一个包含一个或多个必要值的字段的单一类,具体情况而定。
在将大量功能从一个类层次结构移动到另一个地方后,这些更改可能变得必要。当前的层次结构不再那么有价值,其子类现在只是累赘。
好处
- 简化系统架构。如果您只想在不同的方法中返回不同的值,创建子类就是多此一举。
如何重构
-
对子类应用用工厂方法替换构造函数。
-
将子类构造函数调用替换为超类工厂方法调用。
-
在超类中,声明字段以存储每个返回常量值的子类方法的值。
-
创建一个受保护的超类构造函数以初始化新字段。
-
创建或修改现有的子类构造函数,以使它们调用父类的新构造函数并将相关值传递给它。
-
在父类中实现每个常量方法,使其返回对应字段的值。然后从子类中删除该方法。
-
如果子类构造函数具有额外的功能,使用内联方法将构造函数合并到超类工厂方法中。
-
删除子类。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
读累了吗?
难怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的交互式重构课程。这提供了一种不那么乏味的学习新知识的方法。
让我想想…
简化条件表达式
原文:
refactoringguru.cn/refactoring/techniques/simplifying-conditional-expressions
条件语句的逻辑往往随着时间的推移变得越来越复杂,还有更多技术可以应对这一点。
拆分条件
问题: 你有一个复杂的条件(if-then
/else
或 switch
)。
解决方案: 将条件语句中复杂的部分拆分为单独的方法:条件、then
和 else
。
合并条件表达式
问题: 你有多个条件语句导致相同的结果或操作。
解决方案: 将所有这些条件合并为一个表达式。
合并重复的条件片段
问题: 在条件的所有分支中都可以找到相同的代码。
解决方案: 将代码移出条件语句。
移除控制标志
问题: 你有一个布尔变量,它作为多个布尔表达式的控制标志。
解决方案: 不要使用变量,而是使用 break
、continue
和 return
。
用守卫语句替换嵌套条件
问题: 你有一组嵌套的条件语句,很难确定代码执行的正常流程。
解决方案: 将所有特殊检查和边界情况隔离到单独的子句中,并将它们放在主要检查之前。理想情况下,你应该有一个“扁平”的条件列表,一个接一个。
用多态替换条件
问题: 你有一个条件语句,根据对象类型或属性执行不同的操作。
解决方案: 创建与条件分支相匹配的子类。在这些子类中,创建一个共享方法,并将对应条件分支的代码移动到其中。然后用相关的方法调用替换条件语句。结果是,适当的实现将通过多态性根据对象类来获得。
引入空对象
问题: 由于某些方法返回 null
而不是实际对象,你的代码中有许多对 null
的检查。
解决方案: 不要返回 null
,而是返回一个显示默认行为的空对象。
引入断言
问题: 为了使一段代码正常工作,某些条件或值必须为真。
解决方案: 用具体的断言检查替换这些假设。
分解条件
问题
你有一个复杂的条件(if-then
/else
或switch
)。
解决方案
将条件的复杂部分分解为单独的方法:条件、then
和else
。
之前
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
之后
if (isSummer(date)) {
charge = summerCharge(quantity);
}
else {
charge = winterCharge(quantity);
}
之前
if (date < SUMMER_START || date > SUMMER_END)
{
charge = quantity * winterRate + winterServiceCharge;
}
else
{
charge = quantity * summerRate;
}
之后
if (isSummer(date))
{
charge = SummerCharge(quantity);
}
else
{
charge = WinterCharge(quantity);
}
之前
if ($date->before(SUMMER_START) || $date->after(SUMMER_END)) {
$charge = $quantity * $winterRate + $winterServiceCharge;
} else {
$charge = $quantity * $summerRate;
}
之后
if (isSummer($date)) {
$charge = summerCharge($quantity);
} else {
$charge = winterCharge($quantity);
}
之前
if date.before(SUMMER_START) or date.after(SUMMER_END):
charge = quantity * winterRate + winterServiceCharge
else:
charge = quantity * summerRate
之后
if isSummer(date):
charge = summerCharge(quantity)
else:
charge = winterCharge(quantity)
之前
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
}
else {
charge = quantity * summerRate;
}
之后
if (isSummer(date)) {
charge = summerCharge(quantity);
}
else {
charge = winterCharge(quantity);
}
为什么重构
代码越长,理解起来就越困难。当代码充满条件时,事情变得更加难以理解:
-
当你忙于弄清楚
then
块中的代码时,你会忘记相关条件是什么。 -
当你忙于解析
else
时,你会忘记then
中的代码做了什么。
好处
-
通过将条件代码提取到明确命名的方法中,你为将来维护代码的人(比如两个月后的你)简化了工作。
-
这个重构技术也适用于条件中的短表达式。字符串
isSalaryDay()
比用于比较日期的代码要美观且更具描述性。
如何重构
-
通过提取方法将条件提取到单独的方法中。
-
对
then
和else
块重复此过程。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
读腻了吗?
不奇怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的互动重构课程。这提供了一种不那么乏味的学习新知识的方法。
我们来看看…
整合条件表达式
问题
你有多个条件导致相同的结果或动作。
解决方案
将所有这些条件整合到一个表达式中。
之前
double disabilityAmount() {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// Compute the disability amount.
// ...
}
之后
double disabilityAmount() {
if (isNotEligibleForDisability()) {
return 0;
}
// Compute the disability amount.
// ...
}
之前
double DisabilityAmount()
{
if (seniority < 2)
{
return 0;
}
if (monthsDisabled > 12)
{
return 0;
}
if (isPartTime)
{
return 0;
}
// Compute the disability amount.
// ...
}
之后
double DisabilityAmount()
{
if (IsNotEligibleForDisability())
{
return 0;
}
// Compute the disability amount.
// ...
}
之前
function disabilityAmount() {
if ($this->seniority < 2) {
return 0;
}
if ($this->monthsDisabled > 12) {
return 0;
}
if ($this->isPartTime) {
return 0;
}
// compute the disability amount
...
之后
function disabilityAmount() {
if ($this->isNotEligibleForDisability()) {
return 0;
}
// compute the disability amount
...
之前
def disabilityAmount():
if seniority < 2:
return 0
if monthsDisabled > 12:
return 0
if isPartTime:
return 0
# Compute the disability amount.
# ...
之后
def disabilityAmount():
if isNotEligibleForDisability():
return 0
# Compute the disability amount.
# ...
之前
disabilityAmount(): number {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// Compute the disability amount.
// ...
}
之后
disabilityAmount(): number {
if (isNotEligibleForDisability()) {
return 0;
}
// Compute the disability amount.
// ...
}
为什么重构
你的代码包含许多交替的操作符,执行相同的操作。操作符分开的原因并不明确。
整合的主要目的是将条件提取到一个单独的方法中,以获得更大的清晰度。
好处
-
消除了重复的控制流代码。结合多个具有相同“目的地”的条件,有助于表明你只在进行一个复杂的检查,导致一个动作。
-
通过整合所有操作符,你现在可以用一种新的方法将这个复杂表达式隔离开来,其名称解释了条件的目的。
如何重构
在重构之前,确保条件没有任何“副作用”或以其他方式修改某些内容,而只是返回值。副作用可能隐藏在操作符本身内部执行的代码中,例如,当根据条件的结果向变量添加内容时。
-
通过使用
and
和or
将条件整合到一个表达式中。整合时的一般规则是:-
嵌套条件使用
and
连接。 -
连续条件使用
or
连接。
-
-
对操作符条件执行提取方法,并给方法命名以反映表达式的目的。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦阅读?
难怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的交互式重构课程。它提供了一种更轻松的学习新知识的方法。
我们来看看…
合并重复的条件片段
原文:
refactoringguru.cn/consolidate-duplicate-conditional-fragments
问题
相同的代码可以在条件的所有分支中找到。
解决方案
将代码移出条件语句。
之前
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
之后
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();
之前
if (IsSpecialDeal())
{
total = price * 0.95;
Send();
}
else
{
total = price * 0.98;
Send();
}
之后
if (IsSpecialDeal())
{
total = price * 0.95;
}
else
{
total = price * 0.98;
}
Send();
之前
if (isSpecialDeal()) {
$total = $price * 0.95;
send();
} else {
$total = $price * 0.98;
send();
}
之后
if (isSpecialDeal()) {
$total = $price * 0.95;
} else {
$total = $price * 0.98;
}
send();
之前
if isSpecialDeal():
total = price * 0.95
send()
else:
total = price * 0.98
send()
之后
if isSpecialDeal():
total = price * 0.95
else:
total = price * 0.98
send()
之前
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
之后
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();
为什么要重构
在条件的所有分支中发现重复代码,通常是条件分支内代码演变的结果。团队开发可能是导致这一现象的因素之一。
好处
- 代码去重。
如何重构
-
如果重复代码位于条件分支的开头,请将代码移到条件语句之前。
-
如果代码在分支的末尾执行,请将其放置在条件语句之后。
-
如果重复代码随机位于分支内部,首先尝试将代码移到分支的开头或结尾,这取决于它是否会改变后续代码的结果。
-
如果合适,并且重复代码超过一行,尝试使用 提取方法。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
读累了吗?
不奇怪,阅读我们这里所有文本需要 7 小时。
尝试我们的互动重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
移除控制标志
问题
你有一个布尔变量作为多个布尔表达式的控制标志。
解决方案
不要使用变量,使用break
、continue
和return
。
为什么要重构
控制标志可以追溯到古老的编程时代,那时“合格”的程序员总是为他们的函数设置一个入口点(函数声明行)和一个出口点(在函数的最后)。
在现代编程语言中,这种风格的编程已过时,因为我们有特殊的操作符来修改循环和其他复杂结构中的控制流:
-
break
:停止循环。 -
continue
:停止当前循环分支的执行,并在下一个迭代中检查循环条件。 -
return
:停止整个函数的执行并返回其结果(如果在操作符中给出)。
好处
- 控制标志代码通常比使用控制流操作符编写的代码要繁琐得多。
如何重构
-
找到导致退出循环或当前迭代的控制标志的值赋值。
-
如果这是退出循环,则用
break
替换;如果这是退出迭代,则用continue
替换;如果需要从函数返回此值,则用return
替换。 -
删除与控制标志相关的剩余代码和检查。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
读累了吗?
难怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的交互式重构课程。它提供了一种不那么繁琐的学习新知识的方法。
让我们看看……
用保护子句替换嵌套条件
原文:
refactoringguru.cn/replace-nested-conditional-with-guard-clauses
问题
您有一组嵌套条件,难以确定代码执行的正常流程。
解决方案
将所有特殊检查和边界情况隔离到单独的子句中,并将它们放在主要检查之前。理想情况下,您应该有一个“扁平”的条件列表,一个接一个。
之前
public double getPayAmount() {
double result;
if (isDead){
result = deadAmount();
}
else {
if (isSeparated){
result = separatedAmount();
}
else {
if (isRetired){
result = retiredAmount();
}
else{
result = normalPayAmount();
}
}
}
return result;
}
之后
public double getPayAmount() {
if (isDead){
return deadAmount();
}
if (isSeparated){
return separatedAmount();
}
if (isRetired){
return retiredAmount();
}
return normalPayAmount();
}
之前
public double GetPayAmount()
{
double result;
if (isDead)
{
result = DeadAmount();
}
else
{
if (isSeparated)
{
result = SeparatedAmount();
}
else
{
if (isRetired)
{
result = RetiredAmount();
}
else
{
result = NormalPayAmount();
}
}
}
return result;
}
之后
public double GetPayAmount()
{
if (isDead)
{
return DeadAmount();
}
if (isSeparated)
{
return SeparatedAmount();
}
if (isRetired)
{
return RetiredAmount();
}
return NormalPayAmount();
}
之前
function getPayAmount() {
if ($this->isDead) {
$result = $this->deadAmount();
} else {
if ($this->isSeparated) {
$result = $this->separatedAmount();
} else {
if ($this->isRetired) {
$result = $this->retiredAmount();
} else {
$result = $this->normalPayAmount();
}
}
}
return $result;
}
之后
function getPayAmount() {
if ($this->isDead) {
return $this->deadAmount();
}
if ($this->isSeparated) {
return $this->separatedAmount();
}
if ($this->isRetired) {
return $this->retiredAmount();
}
return $this->normalPayAmount();
}
之前
def getPayAmount(self):
if self.isDead:
result = deadAmount()
else:
if self.isSeparated:
result = separatedAmount()
else:
if self.isRetired:
result = retiredAmount()
else:
result = normalPayAmount()
return result
之后
def getPayAmount(self):
if self.isDead:
return deadAmount()
if self.isSeparated:
return separatedAmount()
if self.isRetired:
return retiredAmount()
return normalPayAmount()
之前
getPayAmount(): number {
let result: number;
if (isDead){
result = deadAmount();
}
else {
if (isSeparated){
result = separatedAmount();
}
else {
if (isRetired){
result = retiredAmount();
}
else{
result = normalPayAmount();
}
}
}
return result;
}
之后
getPayAmount(): number {
if (isDead){
return deadAmount();
}
if (isSeparated){
return separatedAmount();
}
if (isRetired){
return retiredAmount();
}
return normalPayAmount();
}
为什么要重构
识别“地狱条件”相对简单。每个嵌套层级的缩进形成一支箭头,指向痛苦与困惑的方向:
if () {
if () {
do {
if () {
if () {
if () {
...
}
}
...
}
...
}
while ();
...
}
else {
...
}
}
很难弄清楚每个条件的作用和如何运作,因为代码执行的“正常”流程并不明显。这些条件表明了混乱的演变,每个条件都是作为权宜之计添加的,而没有考虑到优化整体结构。
为简化情况,将特殊情况隔离到单独的条件中,如果保护子句为真,则立即结束执行并返回一个空值。实际上,您在这里的任务是使结构变得扁平。
如何重构
尝试消除代码中的副作用——将查询与修改分离可能对这个目的有帮助。这个解决方案对于下面描述的重组是必要的。
-
将所有导致调用异常或立即返回值的保护子句隔离出来。将这些条件放在方法的开头。
-
在重排完成并且所有测试成功后,查看是否可以使用合并条件表达式来处理导致相同异常或返回值的保护子句。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
读累了吗?
难怪,阅读这里所有文本需要 7 个小时。
尝试我们的交互式重构课程。它提供了一种不那么繁琐的学习新知识的方法。
让我们看看…
用多态性替换条件
问题
您有一个根据对象类型或属性执行各种操作的条件。
解决方案
创建与条件分支匹配的子类。在这些子类中,创建一个共享方法,并将相应条件分支的代码移到其中。然后用相关方法调用替换条件。最终通过多态性将获得正确的实现,具体取决于对象类。
之前
class Bird {
// ...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}
之后
abstract class Bird {
// ...
abstract double getSpeed();
}
class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.getSpeed();
之前
public class Bird
{
// ...
public double GetSpeed()
{
switch (type)
{
case EUROPEAN:
return GetBaseSpeed();
case AFRICAN:
return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return isNailed ? 0 : GetBaseSpeed(voltage);
default:
throw new Exception("Should be unreachable");
}
}
}
之后
public abstract class Bird
{
// ...
public abstract double GetSpeed();
}
class European: Bird
{
public override double GetSpeed()
{
return GetBaseSpeed();
}
}
class African: Bird
{
public override double GetSpeed()
{
return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue: Bird
{
public override double GetSpeed()
{
return isNailed ? 0 : GetBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.GetSpeed();
之前
class Bird {
// ...
public function getSpeed() {
switch ($this->type) {
case EUROPEAN:
return $this->getBaseSpeed();
case AFRICAN:
return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
case NORWEGIAN_BLUE:
return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
}
throw new Exception("Should be unreachable");
}
// ...
}
之后
abstract class Bird {
// ...
abstract function getSpeed();
// ...
}
class European extends Bird {
public function getSpeed() {
return $this->getBaseSpeed();
}
}
class African extends Bird {
public function getSpeed() {
return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
public function getSpeed() {
return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
}
}
// Somewhere in Client code.
$speed = $bird->getSpeed();
之前
class Bird:
# ...
def getSpeed(self):
if self.type == EUROPEAN:
return self.getBaseSpeed()
elif self.type == AFRICAN:
return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts
elif self.type == NORWEGIAN_BLUE:
return 0 if self.isNailed else self.getBaseSpeed(self.voltage)
else:
raise Exception("Should be unreachable")
之后
class Bird:
# ...
def getSpeed(self):
pass
class European(Bird):
def getSpeed(self):
return self.getBaseSpeed()
class African(Bird):
def getSpeed(self):
return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts
class NorwegianBlue(Bird):
def getSpeed(self):
return 0 if self.isNailed else self.getBaseSpeed(self.voltage)
# Somewhere in client code
speed = bird.getSpeed()
之前
class Bird {
// ...
getSpeed(): number {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new Error("Should be unreachable");
}
}
之后
abstract class Bird {
// ...
abstract getSpeed(): number;
}
class European extends Bird {
getSpeed(): number {
return getBaseSpeed();
}
}
class African extends Bird {
getSpeed(): number {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
getSpeed(): number {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
let speed = bird.getSpeed();
为什么重构
如果您的代码包含根据以下内容执行各种任务的操作符,这种重构技术将有所帮助:
-
对象的类或它实现的接口
-
对象字段的值
-
调用对象方法之一的结果
如果出现新的对象属性或类型,您需要在所有类似条件中搜索并添加代码。因此,如果对象的所有方法中散布着多个条件,这种技术的好处将成倍增加。
好处
-
这种技术遵循告知-不询问原则:与其询问对象的状态并根据此执行操作,不如简单地告诉对象它需要做什么,让它自己决定如何执行。
-
消除重复代码。您摆脱了许多几乎相同的条件。
-
如果需要添加新的执行变体,只需添加一个新子类,而无需修改现有代码(开放/封闭原则)。
如何重构
准备重构
对于这种重构技术,您应该有一个准备好的类层级,包含替代行为。如果没有这样的层级,请创建一个。其他技术将帮助实现这一目标:
-
用子类替换类型代码。将为特定对象属性的所有值创建子类。这种方法简单但灵活性较差,因为您无法为对象的其他属性创建子类。
-
用状态/策略替换类型代码。将为特定对象属性专门创建一个类,并为该属性的每个值从中创建子类。当前类将包含对这种类型对象的引用,并将执行委托给它们。
以下步骤假设您已经创建了层级结构。
重构步骤
-
如果条件在执行其他操作的方法中,请执行提取方法。
-
对于每个层级子类,重定义包含条件的方法,并将相应条件分支的代码复制到该位置。
-
从条件中删除此分支。
-
重复替换直到条件为空。然后删除条件并将方法声明为抽象。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
厌倦阅读了吗?
毫无疑问,阅读我们这里的所有文本需要 7 小时。
尝试我们的交互式重构课程。这提供了一种不那么乏味的学习新知识的方法。
我们来看…
引入空对象
问题
由于一些方法返回null
而不是实际对象,您的代码中有许多对null
的检查。
解决方案
返回空对象而不是null
,使其表现出默认行为。
之前
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
之后
class NullCustomer extends Customer {
boolean isNull() {
return true;
}
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ?
order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
之前
if (customer == null)
{
plan = BillingPlan.Basic();
}
else
{
plan = customer.GetPlan();
}
之后
public sealed class NullCustomer: Customer
{
public override bool IsNull
{
get { return true; }
}
public override Plan GetPlan()
{
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = order.customer ?? new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.GetPlan();
之前
if ($customer === null) {
$plan = BillingPlan::basic();
} else {
$plan = $customer->getPlan();
}
之后
class NullCustomer extends Customer {
public function isNull() {
return true;
}
public function getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
$customer = ($order->customer !== null) ?
$order->customer :
new NullCustomer;
// Use Null-object as if it's normal subclass.
$plan = $customer->getPlan();
之前
if customer is None:
plan = BillingPlan.basic()
else:
plan = customer.getPlan()
之后
class NullCustomer(Customer):
def isNull(self):
return True
def getPlan(self):
return self.NullPlan()
# Some other NULL functionality.
# Replace null values with Null-object.
customer = order.customer or NullCustomer()
# Use Null-object as if it's normal subclass.
plan = customer.getPlan()
之前
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
之后
class NullCustomer extends Customer {
isNull(): boolean {
return true;
}
getPlan(): Plan {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
let customer = (order.customer != null) ?
order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
为什么要重构
对null
的多次检查使您的代码变得更长且更丑。
缺点
- 摆脱条件语句的代价是创建另一个新类。
如何重构
-
从相关类创建一个子类,作为空对象的角色。
-
在两个类中创建方法
isNull()
,该方法对空对象返回true
,对实际类返回false
。 -
找到所有可能返回
null
而不是实际对象的地方。更改代码以返回一个空对象。 -
找到所有将实际类的变量与
null
进行比较的地方。用对isNull()
的调用替换这些检查。 -
-
如果原始类的方法在值不等于
null
的条件下运行,请在空类中重新定义这些方法,并将else
部分的代码插入其中。然后可以删除整个条件,差异化的行为将通过多态性实现。 -
如果事情并不简单且方法无法重新定义,请看看是否可以将原本应该在
null
值情况下执行的操作提取到空对象的新方法中。将这些方法替换为else
中的旧代码作为默认操作。
-
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
读腻了吗?
难怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的互动重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
引入断言
问题
为了让一段代码正确工作,某些条件或值必须为真。
解决方案
用具体的断言检查替换这些假设。
之前
double getExpenseLimit() {
// Should have either expense limit or
// a primary project.
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit :
primaryProject.getMemberExpenseLimit();
}
之后
double getExpenseLimit() {
Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
之前
double GetExpenseLimit()
{
// Should have either expense limit or
// a primary project.
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit :
primaryProject.GetMemberExpenseLimit();
}
之后
double GetExpenseLimit()
{
Assert.IsTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.GetMemberExpenseLimit();
}
之前
function getExpenseLimit() {
// Should have either expense limit or
// a primary project.
return ($this->expenseLimit !== NULL_EXPENSE) ?
$this->expenseLimit:
$this->primaryProject->getMemberExpenseLimit();
}
之后
function getExpenseLimit() {
assert($this->expenseLimit !== NULL_EXPENSE || isset($this->primaryProject));
return ($this->expenseLimit !== NULL_EXPENSE) ?
$this->expenseLimit:
$this->primaryProject->getMemberExpenseLimit();
}
之前
def getExpenseLimit(self):
# Should have either expense limit or
# a primary project.
return self.expenseLimit if self.expenseLimit != NULL_EXPENSE else \
self.primaryProject.getMemberExpenseLimit()
之后
def getExpenseLimit(self):
assert (self.expenseLimit != NULL_EXPENSE) or (self.primaryProject != None)
return self.expenseLimit if (self.expenseLimit != NULL_EXPENSE) else \
self.primaryProject.getMemberExpenseLimit()
之前
getExpenseLimit(): number {
// Should have either expense limit or
// a primary project.
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
之后
getExpenseLimit(): number {
// TypeScript and JS doesn't have built-in assertions, so we'll use
// good-old console.error(). You can always extract this into a
// designated assertion function.
if (!(expenseLimit != NULL_EXPENSE ||
(typeof primaryProject !== 'undefined' && primaryProject))) {
console.error("Assertion failed: getExpenseLimit()");
}
return (expenseLimit != NULL_EXPENSE) ?
expenseLimit:
primaryProject.getMemberExpenseLimit();
}
为什么重构
假设一段代码假设了某个对象的当前状态或参数或局部变量的值。通常,这种假设在出现错误时才会失效。
通过添加相应的断言使这些假设变得明显。与方法参数中的类型提示一样,这些断言可以作为代码的实时文档。
作为检查代码需要断言的指导方针,请查看描述特定方法工作条件的注释。
好处
- 如果一个假设不成立,导致代码给出错误结果,那么最好在此之前停止执行,以免造成致命后果和数据损坏。这也意味着在设计测试程序时,你忽略了写必要的测试。
缺点
-
有时候,抛出异常比简单的断言更合适。你可以选择必要的异常类,并让其余代码正确处理它。
-
什么时候异常比简单断言更好?如果异常可以由用户或系统的操作引起,并且你能够处理该异常。另一方面,普通的未命名和未处理的异常基本上等同于简单的断言——你不处理它们,它们是程序错误的结果,这种错误本不该发生。
如何重构
当你看到某个条件被假设时,添加对此条件的断言以确保其正确性。
添加断言不应改变程序的行为。
不要在代码的所有地方过度使用断言。只检查对代码正确运行所必需的条件。如果你的代码即使在某个特定断言为假时仍能正常工作,你可以安全地移除该断言。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
读累了吗?
难怪,阅读我们这里所有文本需要 7 个小时。
尝试我们的互动重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
简化方法调用
原文:
refactoringguru.cn/refactoring/techniques/simplifying-method-calls
这些技术使方法调用变得更简单、更易理解。这反过来又简化了类之间交互的接口。
重命名方法
问题: 一个方法的名称未能解释该方法的功能。
解决方案: 重命名该方法。
添加参数
问题: 一个方法没有足够的数据来执行某些操作。
解决方案: 创建一个新参数来传递必要的数据。
移除参数
问题: 一个参数在方法体内未被使用。
解决方案: 移除未使用的参数。
将查询与修改分开
问题: 你是否有一个返回值的方法,同时还改变了对象内部的某些内容?
解决方案: 将方法拆分为两个独立的方法。正如你所期待的,其中一个应返回值,另一个则修改对象。
参数化方法
问题: 多个方法执行类似的操作,仅在其内部值、数字或操作上有所不同。
解决方案: 通过使用一个参数来传递必要的特殊值,将这些方法合并。
用显式方法替代参数
问题: 一个方法被分成多个部分,每个部分的执行依赖于一个参数的值。
解决方案: 将方法的各个部分提取到它们自己的方法中,并调用这些方法,而不是原始方法。
保持整个对象
问题: 你从一个对象中获取多个值,然后将它们作为参数传递给一个方法。
解决方案: 相反,尝试传递整个对象。
用方法调用替代参数
问题: 调用查询方法并将其结果作为参数传递给另一个方法,而该方法本可以直接调用查询。
解决方案: 尝试在方法体内放置查询调用,而不是通过参数传递值。
引入参数对象
问题: 你的方法包含一组重复的参数。
解决方案: 用一个对象替代这些参数。
移除设置方法
问题: 字段的值应在创建时设置,并且之后不应改变。
解决方案: 因此,移除设置字段值的方法。
隐藏方法
问题: 一个方法未被其他类使用,或仅在其自己的类层次内使用。
解决方案: 将该方法设置为私有或受保护。
用工厂方法替代构造函数
问题: 你有一个复杂的构造函数,它不仅仅是设置对象字段中的参数值。
解决方案: 创建一个工厂方法,用它替换构造函数调用。
用异常替换错误代码
问题: 一个方法返回一个特殊值来表示错误吗?
解决方案: 改为抛出异常。
用测试替换异常
问题: 你在一个简单测试可以解决的地方抛出异常吗?
解决方案: 用条件测试替换异常。
重命名方法
问题
方法的名称并没有解释该方法的功能。
解决方案
重命名方法。
在此之前!重命名方法 - 之前之后!重命名方法 - 之后
为什么重构
也许这个方法从一开始就命名不当——例如,有人匆忙创建了该方法,并没有认真对待命名。
或者,也许该方法起初命名得当,但随着其功能的增加,方法名称就不再是一个好的描述符。
益处
- 代码可读性。尝试给新方法起一个反映其功能的名称。类似于
createOrder()
、renderCustomerInfo()
等。
如何重构
-
查看该方法是否在超类或子类中定义。如果是,你也必须在这些类中重复所有步骤。
-
下一个方法在重构过程中对保持程序的功能至关重要。创建一个新名称的新方法。将旧方法的代码复制到其中。删除旧方法中的所有代码,并在其位置插入对新方法的调用。
-
找到所有对旧方法的引用,并用对新方法的引用替换它们。
-
删除旧方法。如果旧方法是公共接口的一部分,则不要执行此步骤。相反,将旧方法标记为不推荐使用。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦阅读吗?
难怪,阅读我们这里的所有文本需要 7 个小时。
尝试我们的交互式重构课程。这提供了一种不那么乏味的学习新知识的方法。
让我们看看…
添加参数
问题
方法没有足够的数据来执行某些操作。
解决方案
创建一个新参数以传递必要的数据。
之前!添加参数 - 之前之后!添加参数 - 之后
为什么重构
您需要对方法进行更改,而这些更改需要添加以前未提供给该方法的信息或数据。
好处
- 在这里的选择是添加一个新参数还是添加一个包含方法所需数据的新私有字段。当您需要一些偶尔或频繁变化的数据,而不必一直将其保留在对象中时,使用参数更为合适。在这种情况下,重构会带来收益。否则,添加一个私有字段,并在调用方法之前用必要的数据填充它。
缺点
-
添加一个新参数总是比删除它容易,这就是为什么参数列表经常膨胀到离谱的大小。这种现象被称为长参数列表。
-
如果您需要添加一个新参数,这有时意味着您的类不包含必要的数据,或者现有参数不包含必要的相关数据。在这两种情况下,最佳解决方案是考虑将数据移动到主类或其他可以在方法内部访问的类中。
如何重构
-
查看该方法是否在超类或子类中定义。如果该方法存在于它们中,您需要在这些类中重复所有步骤。
-
接下来的步骤对保持程序在重构过程中的功能至关重要。通过复制旧方法创建一个新方法,并为其添加必要的参数。用对新方法的调用替换旧方法的代码。您可以将任何值插入到新参数中(例如,对于对象使用
null
,对于数字使用零)。 -
查找所有对旧方法的引用,并将其替换为对新方法的引用。
-
删除旧方法。如果旧方法是公共接口的一部分,则无法删除。在这种情况下,将旧方法标记为已弃用。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
厌倦阅读了吗?
不奇怪,阅读我们这里的所有文本需要 7 小时。
试试我们的交互式重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
移除参数
问题
参数在方法体中未被使用。
解决方案
移除未使用的参数。
之前之后
为什么重构
方法调用中的每个参数都迫使程序员理解该参数中包含的信息。如果一个参数在方法体中完全未使用,这种“思考”就毫无意义。
而且,在任何情况下,额外的参数都是必须执行的额外代码。
有时我们会添加一些参数,以便将来可能需要这些参数来应对方法的变化。然而,经验表明,只有在真正需要时才添加参数更为妥当。毕竟,预期的变化往往只是预期而已。
好处
- 方法只包含真正需要的参数。
什么时候不使用
- 如果该方法在子类或超类中以不同方式实现,并且你的参数在这些实现中被使用,请保持参数不变。
如何重构
-
查看该方法是否在超类或子类中定义。如果是,参数在那里是否被使用?如果参数在这些实现中被使用,则应暂缓使用此重构技术。
-
下一步对于在重构过程中保持程序功能至关重要。通过复制旧方法创建一个新方法,并从中删除相关参数。用对新方法的调用替换旧方法的代码。
-
找到所有对旧方法的引用,并用对新方法的引用替换它们。
-
删除旧方法。如果旧方法是公共接口的一部分,则不要执行此步骤。在这种情况下,将旧方法标记为弃用。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
读累了吗?
难怪,阅读我们这里所有的文本需要 7 小时。
尝试我们的互动重构课程。它提供了一种不那么乏味的学习新知识的方法。
我们来看看…
将查询与修改分开
问题
你是否有一个返回值但也修改对象内部的某个方法?
解决方案
将该方法拆分为两个独立的方法。正如你所预期的,一个返回值,另一个修改对象。
之前之后
为什么要重构
该重构技术实现了命令与查询职责分离。这个原则告诉我们将负责获取数据的代码与改变对象内部某些内容的代码分开。
获取数据的代码称为查询。改变对象可见状态的代码称为修改器。当查询与修改器结合时,你无法在不改变条件的情况下获取数据。换句话说,你提出问题时可能会改变答案,即使在接收时。这一问题在调用查询的人可能不知道该方法的“副作用”时变得更加严重,这常常导致运行时错误。
但请记住,副作用在改变对象可见状态的修改器的情况下是危险的。这可能是例如,从对象的公共接口可访问的字段、数据库中的条目、文件等。如果修改器仅缓存复杂操作并将其保存在类的私有字段中,它几乎不会导致任何副作用。
好处
- 如果你有一个查询,它不改变程序的状态,你可以随意调用它,而不用担心调用该方法会导致的意外结果变化。
缺点
- 在某些情况下,在执行命令后获取数据是方便的。例如,从数据库中删除某些内容时,你想知道删除了多少行。
如何重构
-
创建一个新的查询方法,以返回原方法所做的内容。
-
修改原方法,使其仅返回调用新查询方法的结果。
-
用对查询方法的调用替换对原方法的所有引用。在这一行之前,放置对修改方法的调用。如果原方法在条件运算符或循环的条件中被使用,这将避免副作用。
-
消除原方法中返回值的代码,该方法现在已成为适当的修改方法。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
读累了吗?
不难理解,阅读我们这里的所有文本需要 7 小时。
试试我们的互动重构课程。它提供了一种不那么枯燥的学习新知识的方法。
我们来看一下…
参数化方法
问题
多个方法执行的操作相似,仅在其内部值、数字或操作上有所不同。
解决方案
通过使用一个参数来传递必要的特殊值来合并这些方法。
在此之前!参数化方法 - 之前之后!参数化方法 - 之后
为什么重构
如果你有类似的方法,你可能会有重复的代码,带来所有相应的后果。
更重要的是,如果你需要再添加一个功能的版本,你将不得不创建另一个方法。相反,你可以简单地用不同的参数运行现有的方法。
缺点
-
有时这种重构技术可能会走得太远,导致生成一个长而复杂的公共方法,而不是多个简单的方法。
-
当将功能的激活/停用移动到参数时,也要小心。这最终可能导致创建一个大型条件运算符,需要通过用显式方法替换参数来处理。
如何重构
-
创建一个带参数的新方法,并将其移至所有类通用的代码中,通过应用提取方法。注意,有时方法的某一部分实际上是相同的。在这种情况下,重构就是将相同的部分提取到一个新方法中。
-
在新方法的代码中,用参数替换特殊/不同的值。
-
对于每个旧方法,找到它被调用的地方,将这些调用替换为包含参数的新方法的调用。然后删除旧方法。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦了阅读?
毫无疑问,阅读我们这里所有文本需要 7 个小时。
尝试我们的互动重构课程。这提供了一种不那么乏味的学习新知识的方法。
让我们看看…
用显式方法替换参数
原文:
refactoringguru.cn/replace-parameter-with-explicit-methods
问题
方法被拆分为几个部分,每个部分根据参数的值运行。
解决方案
将方法的各个部分提取到自己的方法中,并调用它们来替代原始方法。
之前
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
之后
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
之前
void SetValue(string name, int value)
{
if (name.Equals("height"))
{
height = value;
return;
}
if (name.Equals("width"))
{
width = value;
return;
}
Assert.Fail();
}
之后
void SetHeight(int arg)
{
height = arg;
}
void SetWidth(int arg)
{
width = arg;
}
之前
function setValue($name, $value) {
if ($name === "height") {
$this->height = $value;
return;
}
if ($name === "width") {
$this->width = $value;
return;
}
assert("Should never reach here");
}
之后
function setHeight($arg) {
$this->height = $arg;
}
function setWidth($arg) {
$this->width = $arg;
}
之前
def output(self, type):
if name == "banner"
# Print the banner.
# ...
if name == "info"
# Print the info.
# ...
之后
def outputBanner(self):
# Print the banner.
# ...
def outputInfo(self):
# Print the info.
# ...
之前
setValue(name: string, value: number): void {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
}
之后
setHeight(arg: number): void {
height = arg;
}
setWidth(arg: number): number {
width = arg;
}
为什么重构
一个包含依赖参数变体的方法变得庞大。每个分支中都运行非平凡的代码,并且新变体很少被添加。
好处
- 提高代码可读性。理解
startEngine()
的目的要比理解setValue("engineEnabled", true)
容易得多。
何时不使用
- 如果一个方法很少更改且不会添加新变体,则不要用显式方法替换参数。
如何重构
-
对于每种方法变体,创建一个单独的方法。根据主方法中参数的值运行这些方法。
-
找到所有调用原始方法的地方。在这些地方,调用其中一个新的依赖参数的变体。
-
当没有原始方法的调用时,删除它。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
您的浏览器不支持 HTML 视频。
读得累吗?
不奇怪,阅读我们这里所有的文本需要 7 小时。
尝试我们的交互式重构课程。这提供了一种不那么乏味的学习新知识的方法。
让我们看看……
保持整个对象
问题
你从一个对象获取多个值,然后将它们作为参数传递给一个方法。
解决方案
相反,试着传递整个对象。
之前
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
之后
boolean withinPlan = plan.withinRange(daysTempRange);
之前
int low = daysTempRange.GetLow();
int high = daysTempRange.GetHigh();
bool withinPlan = plan.WithinRange(low, high);
之后
bool withinPlan = plan.WithinRange(daysTempRange);
之前
$low = $daysTempRange->getLow();
$high = $daysTempRange->getHigh();
$withinPlan = $plan->withinRange($low, $high);
之后
$withinPlan = $plan->withinRange($daysTempRange);
之前
low = daysTempRange.getLow()
high = daysTempRange.getHigh()
withinPlan = plan.withinRange(low, high)
之后
withinPlan = plan.withinRange(daysTempRange)
之前
let low = daysTempRange.getLow();
let high = daysTempRange.getHigh();
let withinPlan = plan.withinRange(low, high);
之后
let withinPlan = plan.withinRange(daysTempRange);
为什么重构
问题是每次在调用方法之前,未来参数对象的方法都必须被调用。如果这些方法或获取的数据量发生变化,你需要仔细找到程序中十几个这样的地方,并在每个地方实施这些更改。
在应用此重构技术后,获取所有必要数据的代码将存储在一个地方——方法本身。
好处
-
你看到的不是一堆杂乱无章的参数,而是一个具有可理解名称的单一对象。
-
如果方法需要从一个对象中获取更多数据,你不需要重写所有使用该方法的地方——只需在方法内部进行修改。
缺点
- 有时这种转变会导致方法变得不那么灵活:之前方法可以从许多不同来源获取数据,但现在由于重构,我们将其使用限制在只有特定接口的对象。
如何重构
-
在方法中创建一个参数,以获取所需值的对象。
-
现在开始逐一删除方法中的旧参数,用参数对象相关方法的调用替换它们。每替换一个参数后测试程序。
-
从参数对象中删除在方法调用之前的获取器代码。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦阅读了吗?
难怪,阅读我们这里所有的文本需要 7 小时。
尝试我们的交互式重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看…
用方法调用替换参数
问题
调用查询方法并将其结果作为另一个方法的参数,而那个方法本可以直接调用查询。
解决方案
不要通过参数传递值,尝试在方法体内放置查询调用。
之前
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
之后
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
之前
int basePrice = quantity * itemPrice;
double seasonDiscount = this.GetSeasonalDiscount();
double fees = this.GetFees();
double finalPrice = DiscountedPrice(basePrice, seasonDiscount, fees);
之后
int basePrice = quantity * itemPrice;
double finalPrice = DiscountedPrice(basePrice);
之前
$basePrice = $this->quantity * $this->itemPrice;
$seasonDiscount = $this->getSeasonalDiscount();
$fees = $this->getFees();
$finalPrice = $this->discountedPrice($basePrice, $seasonDiscount, $fees);
之后
$basePrice = $this->quantity * $this->itemPrice;
$finalPrice = $this->discountedPrice($basePrice);
之前
basePrice = quantity * itemPrice
seasonalDiscount = self.getSeasonalDiscount()
fees = self.getFees()
finalPrice = discountedPrice(basePrice, seasonalDiscount, fees)
之后
basePrice = quantity * itemPrice
finalPrice = discountedPrice(basePrice)
之前
let basePrice = quantity * itemPrice;
const seasonDiscount = this.getSeasonalDiscount();
const fees = this.getFees();
const finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
之后
let basePrice = quantity * itemPrice;
let finalPrice = discountedPrice(basePrice);
为什么要重构
一长串参数很难理解。此外,对这样的方式的调用往往类似于一系列的级联,复杂且令人兴奋的值计算难以导航,但必须传递给方法。因此,如果参数值可以借助某个方法计算,请在方法内部执行此操作,去掉参数。
好处
- 我们去掉不必要的参数,简化方法调用。这些参数通常不是为了当前的项目而创建,而是为了未来可能永远不会到来的需求。
缺点
- 你可能明天需要这个参数以满足其他需求……这会让你重新编写方法。
如何重构
-
确保获取值的代码不使用当前方法的参数,因为它们在另一个方法内部不可用。如果是这样,移动代码就不可能。
-
如果相关代码比单个方法或函数调用更复杂,请使用 提取方法 将这些代码隔离到一个新方法中,使调用更简单。
-
在主方法的代码中,将所有对被替换参数的引用替换为获取值的方法调用。
-
使用 移除参数 来消除现在未使用的参数。
</images/refactoring/banners/tired-of-reading-banner-1x.mp4?id=7fa8f9682afda143c2a491c6ab1c1e56>
</images/refactoring/banners/tired-of-reading-banner.png?id=1721d160ff9c84cbf8912f5d282e2bb4>
你的浏览器不支持 HTML 视频。
厌倦阅读了吗?
难怪,阅读我们这里所有文本需要 7 个小时。
尝试我们的交互式重构课程。它提供了一种不那么乏味的学习新知识的方法。
让我们看看……
标签:重构,return,代码,else,方法,self,大师 From: https://www.cnblogs.com/apachecn/p/18486881