Switch 声明
Switch 声明(Switch Statements)
你有一个复杂的 switch 语句或 if 序列语句。
问题原因
面向对象程序的一个最明显特征就是:少用 switch 和 case 语句。从本质上说,switch 语句的问题在于重复(if 序列也同样如此)。你常会发现 switch 语句散布于不同地点。如果要为它添加一个新的 case 子句,就必须找到所有 switch语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。
大多数时候,一看到 switch 语句,就应该考虑以多态来替换它。
解决方法
问题是多态该出现在哪?switch 语句常常根据类型码进行选择,你要的是“与该类型码相关的函数或类”,所以应该运用 提炼函数(Extract Method) 将 switch 语句提炼到一个独立函数中,再以 搬移函数(Move Method) 将它搬移到需要多态性的那个类里。
如果你的 switch 是基于类型码来识别分支,这时可以运用 以子类取代类型码(Replace Type Code with Subclass) 或 以状态/策略模式取代类型码(Replace Type Code with State/Strategy) 。
一旦完成这样的继承结构后,就可以运用 以多态取代条件表达式(Replace Conditional with Polymorphism) 了。
如果条件分支并不多并且它们使用不同参数调用相同的函数,多态就没必要了。在这种情况下,你可以运用 以明确函数取代参数(Replace Parameter with Explicit Methods) 。
如果你的选择条件之一是 null,可以运用 引入 Null 对象(Introduce Null Object) 。
收益
提升代码组织性。
何时忽略
如果一个 switch 操作只是执行简单的行为,就没有重构的必要了。
switch 常被工厂设计模式族(工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory))所使用,这种情况下也没必要重构。
重构方法说明
提炼函数(Extract Method)
问题
你有一段代码可以组织在一起。
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
解决
移动这段代码到一个新的函数中,使用函数的调用来替代老代码。
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
搬移函数(Move Method)
问题
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。
以子类取代类型码(Replace Type Code with Subclass)
问题
你有一个不可变的类型码,它会影响类的行为。
解决
以子类取代这个类型码。
以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
问题
你有一个类型码,它会影响类的行为,但你无法通过继承消除它。
解决
以状态对象取代类型码。
以多态取代条件表达式(Replace Conditional with Polymorphism)
问题
你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。
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();
以明确函数取代参数(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;
}
引入 Null 对象(Introduce Null Object)
问题
你需要再三检查某对象是否为 null。
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
解决
将 null 值替换为 null 对象。
class NullCustomer extends Customer {
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();
临时字段
临时字段(Temporary Field)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。
问题原因
有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。 通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处。 这种代码不好理解。你期望查看对象字段的数据,但是出于某种原因,它们总是为空。
解决方法
可以通过 提炼类(Extract Class) 将临时字段和操作它们的所有代码提炼到一个单独的类中。此外,你可以运用 以函数对象取代函数(Replace Method with Method Object) 来实现同样的目的。
引入 Null 对象(Introduce Null Object) 在“变量不合法”的情况下创建一个 null 对象,从而避免写出条件表达式。
收益
更好的代码清晰度和组织性。
重构方法说明
提炼类(Extract Class)
问题
某个类做了不止一件事。
解决
建立一个新类,将相关的字段和函数从旧类搬移到新类。
以函数对象取代函数(Replace Method with Method Object)
问题
你有一个过长函数,它的局部变量交织在一起,以致于你无法应用提炼函数(Extract Method) 。
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
解决
将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}
public double compute() {
// long computation.
//...
}
}
引入 Null 对象(Introduce Null Object)
问题
你需要再三检查某对象是否为 null。
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
解决
将 null 值替换为 null 对象。
class NullCustomer extends Customer {
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();
异曲同工的类
异曲同工的类(Alternative Classes with Different Interfaces)
两个类中有着不同的函数,却在做着同一件事。
问题原因
这种情况往往是因为:创建这个类的程序员并不知道已经有实现这个功能的类存在了。
解决方法
如果两个函数做同一件事,却有着不同的签名,请运用 函数改名(Rename Method) 根据它们的用途重新命名。
运用 搬移函数(Move Method) 、 添加参数(Add Parameter) 和 令函数携带参数(Parameterize Method) 来使得方法的名称和实现一致。
如果两个类仅有部分功能是重复的,尝试运用 提炼超类(Extract Superclass) 。这种情况下,已存在的类就成了超类。
当最终选择并运用某种方法来重构后,也许你就能删除其中一个类了。
收益
消除了不必要的重复代码,为代码瘦身了。
代码更易读(不再需要猜测为什么要有两个功能相同的类)。
何时忽略
有时合并类是不可能的,或者是如此困难以至于没有意义。例如:两个功能相似的类存在于不同的 lib 库中。
重构方法说明
函数改名(Rename Method)
问题
函数的名称未能恰当的揭示函数的用途。
class Person {
public String getsnm();
}
解决
修改函数名。
class Person {
public String getSecondName();
}
搬移函数(Move Method)
问题
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。
添加参数(Add Parameter)
问题 某个函数需要从调用端得到更多信息。
class Customer {
public Contact getContact();
}
解决 为此函数添加一个对象函数,让改对象带进函数所需信息。
class Customer {
public Contact getContact(Date date);
}
令函数携带参数(Parameterize Method)
问题
若干函数做了类似的工作,但在函数本体中却包含了不同的值。
解决
建立单一函数,以参数表达哪些不同的值。
提炼超类(Extract Superclass)
问题
两个类有相似特性。
解决
为这两个类建立一个超类,将相同特性移至超类。
被拒绝的馈赠
被拒绝的馈赠(Refused Bequest)
子类仅仅使用父类中的部分方法和属性。其他来自父类的馈赠成为了累赘。
问题原因
有些人仅仅是想重用超类中的部分代码而创建了子类。但实际上超类和子类完全不同。
解决方法
如果继承没有意义并且子类和父类之间确实没有共同点,可以运用 以委托取代继承(Replace Inheritance with Delegation) 消除继承。
如果继承是适当的,则去除子类中不需要的字段和方法。运用 提炼超类(Extract Superclass) 将所有超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,然后让两个类都继承自它。
收益
提高代码的清晰度和组织性。
重构方法说明
以委托取代继承(Replace Inheritance with Delegation)
问题
某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。
解决
在子类中新建一个字段用以保存超类;
调整子类函数,令它改而委托超类;
然后去掉两者之间的继承关系。
提炼超类(Extract Superclass)
问题
两个类有相似特性。
解决
为这两个类建立一个超类,将相同特性移至超类。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。