开发中滥用面向对象,你是否违背了编程原则

Switch 声明
Switch 声明(Switch Statements)程序员

你有一个复杂的 switch 语句或 if 序列语句。算法

clipboard.png

问题缘由
面向对象程序的一个最明显特征就是:少用 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) 。
收益
提高代码组织性。this

clipboard.png

什么时候忽略
若是一个 switch 操做只是执行简单的行为,就没有重构的必要了。
switch 常被工厂设计模式族(工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory))所使用,这种状况下也不必重构。
重构方法说明
提炼函数(Extract Method)
问题spa

你有一段代码能够组织在一块儿。设计

void printOwing() {
printBanner();code

//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
解决orm

移动这段代码到一个新的函数中,使用函数的调用来替代老代码。对象

void printOwing() {
printBanner();
printDetails(getOutstanding());
}

void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
搬移函数(Move Method)
问题

你的程序中,有个函数与其所驻类以外的另外一个类进行更多交流:调用后者,或被后者调用。

clipboard.png

解决

在该函数最常引用的类中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数彻底移除。

clipboard.png

以子类取代类型码(Replace Type Code with Subclass)
问题

你有一个不可变的类型码,它会影响类的行为。

clipboard.png

解决

以子类取代这个类型码。

clipboard.png

以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
问题

你有一个类型码,它会影响类的行为,但你没法经过继承消除它。

clipboard.png

解决

以状态对象取代类型码。

clipboard.png

以多态取代条件表达式(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)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。

clipboard.png

问题缘由
有时你会看到这样的对象:其内某个实例变量仅为某种特定状况而设。这样的代码让人不易理解,由于你一般认为对象在全部时候都须要它的全部变量。在变量未被使用的状况下猜想当初设置目的,会让你发疯。 一般,临时字段是在某一算法须要大量输入时而建立。所以,为了不函数有过多参数,程序员决定在类中建立这些数据的临时字段。这些临时字段仅仅在算法中使用,其余时候却毫无用处。 这种代码很差理解。你指望查看对象字段的数据,可是出于某种缘由,它们老是为空。

解决方法
能够经过 提炼类(Extract Class) 将临时字段和操做它们的全部代码提炼到一个单独的类中。此外,你能够运用 以函数对象取代函数(Replace Method with Method Object) 来实现一样的目的。
引入 Null 对象(Introduce Null Object) 在“变量不合法”的状况下建立一个 null 对象,从而避免写出条件表达式。

clipboard.png

收益
更好的代码清晰度和组织性。

clipboard.png

重构方法说明
提炼类(Extract Class)
问题

某个类作了不止一件事。

clipboard.png

解决

创建一个新类,将相关的字段和函数从旧类搬移到新类。

clipboard.png

以函数对象取代函数(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)

两个类中有着不一样的函数,却在作着同一件事。

clipboard.png

问题缘由
这种状况每每是由于:建立这个类的程序员并不知道已经有实现这个功能的类存在了。

解决方法
若是两个函数作同一件事,却有着不一样的签名,请运用 函数更名(Rename Method) 根据它们的用途从新命名。
运用 搬移函数(Move Method) 、 添加参数(Add Parameter) 和 令函数携带参数(Parameterize Method) 来使得方法的名称和实现一致。
若是两个类仅有部分功能是重复的,尝试运用 提炼超类(Extract Superclass) 。这种状况下,已存在的类就成了超类。
当最终选择并运用某种方法来重构后,也许你就能删除其中一个类了。
收益
消除了没必要要的重复代码,为代码瘦身了。
代码更易读(再也不须要猜想为何要有两个功能相同的类)。

clipboard.png

什么时候忽略
有时合并类是不可能的,或者是如此困难以致于没有意义。例如:两个功能类似的类存在于不一样的 lib 库中。
重构方法说明
函数更名(Rename Method)
问题

函数的名称未能恰当的揭示函数的用途。

class Person {
public String getsnm();
}
解决

修改函数名。

class Person {
public String getSecondName();
}
搬移函数(Move Method)
问题

你的程序中,有个函数与其所驻类以外的另外一个类进行更多交流:调用后者,或被后者调用。

clipboard.png

解决

在该函数最常引用的类中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数彻底移除。

clipboard.png

添加参数(Add Parameter)
问题 某个函数须要从调用端获得更多信息。

class Customer {
public Contact getContact();
}
解决 为此函数添加一个对象函数,让改对象带进函数所需信息。

class Customer {
public Contact getContact(Date date);
}
令函数携带参数(Parameterize Method)
问题

若干函数作了相似的工做,但在函数本体中却包含了不一样的值。

clipboard.png

解决

创建单一函数,以参数表达哪些不一样的值。

clipboard.png

提炼超类(Extract Superclass)
问题

两个类有类似特性。

clipboard.png

解决

为这两个类创建一个超类,将相同特性移至超类。

clipboard.png

被拒绝的馈赠
被拒绝的馈赠(Refused Bequest)

子类仅仅使用父类中的部分方法和属性。其余来自父类的馈赠成为了累赘。

clipboard.png

问题缘由
有些人仅仅是想重用超类中的部分代码而建立了子类。但实际上超类和子类彻底不一样。

解决方法
若是继承没有意义而且子类和父类之间确实没有共同点,能够运用 以委托取代继承(Replace Inheritance with Delegation) 消除继承。
若是继承是适当的,则去除子类中不须要的字段和方法。运用 提炼超类(Extract Superclass) 将全部超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,而后让两个类都继承自它。

clipboard.png

收益
提升代码的清晰度和组织性。

clipboard.png

重构方法说明
以委托取代继承(Replace Inheritance with Delegation)
问题

某个子类只使用超类接口中的一部分,或是根本不须要继承而来的数据。

clipboard.png

解决

在子类中新建一个字段用以保存超类;
调整子类函数,令它改而委托超类;
而后去掉二者之间的继承关系。

clipboard.png

提炼超类(Extract Superclass)
问题

两个类有类似特性。

clipboard.png

解决

为这两个类创建一个超类,将相同特性移至超类。

clipboard.png
图片描述

相关文章
相关标签/搜索