:notebook: 本文已归档到:「blog」java
翻译自:https://sourcemaking.com/refactoring/smells/oo-abusersgit
滥用面向对象(Object-Orientation Abusers)这组坏味道意味着:代码部分或彻底地违背了面向对象编程原则。程序员
Switch 声明(Switch Statements)github
你有一个复杂的
switch
语句或if
序列语句。算法
面向对象程序的一个最明显特征就是:少用 switch
和 case
语句。从本质上说,switch
语句的问题在于重复(if
序列也一样如此)。你常会发现 switch
语句散布于不一样地点。若是要为它添加一个新的 case
子句,就必须找到全部 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 对象(Introduce Null Object)
。switch
操做只是执行简单的行为,就没有重构的必要了。switch
常被工厂设计模式族(工厂方法模式(Factory Method)
和抽象工厂模式(Abstract Factory)
)所使用,这种状况下也不必重构。问题bash
你有一段代码能够组织在一块儿。函数
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
复制代码
解决post
移动这段代码到一个新的函数中,使用函数的调用来替代老代码。
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
复制代码
问题
你的程序中,有个函数与其所驻类以外的另外一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数彻底移除。
问题
你有一个不可变的类型码,它会影响类的行为。
解决
以子类取代这个类型码。
问题
你有一个类型码,它会影响类的行为,但你没法经过继承消除它。
解决
以状态对象取代类型码。
问题
你手上有个条件表达式,它根据对象类型的不一样而选择不一样的行为。
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();
复制代码
问题
你有一个函数,其中彻底取决于参数值而采起不一样的行为。
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。
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 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。
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)
。这种状况下,已存在的类就成了超类。问题
函数的名称未能恰当的揭示函数的用途。
class Person {
public String getsnm();
}
复制代码
解决
修改函数名。
class Person {
public String getSecondName();
}
复制代码
问题
你的程序中,有个函数与其所驻类以外的另外一个类进行更多交流:调用后者,或被后者调用。
解决
在该函数最常引用的类中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数彻底移除。
问题 某个函数须要从调用端获得更多信息。
class Customer {
public Contact getContact();
}
复制代码
解决 为此函数添加一个对象函数,让改对象带进函数所需信息。
class Customer {
public Contact getContact(Date date);
}
复制代码
问题
若干函数作了相似的工做,但在函数本体中却包含了不一样的值。
创建单一函数,以参数表达哪些不一样的值。
问题
两个类有类似特性。
解决
为这两个类创建一个超类,将相同特性移至超类。
被拒绝的馈赠(Refused Bequest)
子类仅仅使用父类中的部分方法和属性。其余来自父类的馈赠成为了累赘。
有些人仅仅是想重用超类中的部分代码而建立了子类。但实际上超类和子类彻底不一样。
以委托取代继承(Replace Inheritance with Delegation)
消除继承。提炼超类(Extract Superclass)
将全部超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,而后让两个类都继承自它。问题
某个子类只使用超类接口中的一部分,或是根本不须要继承而来的数据。
解决
问题
两个类有类似特性。
解决
为这两个类创建一个超类,将相同特性移至超类。