重构坏代码篇-异曲同工的类
**异曲同工的类(Divergent Change)**是一种代码坏味道,它指的是多个类在不同的方向上发生变化,即一个类的修改会导致其他类也需要相应的修改。
下面是一个 Java 示例:
// 异曲同工的类示例
// 原始类A
class A {
private int x;
public void setX(int value) {
x = value;
}
public int calculate() {
// 计算逻辑
return x * 2;
}
}
// 原始类B
class B {
private int y;
public void setY(int value) {
y = value;
}
public int calculate() {
// 计算逻辑
return y * 3;
}
}
// 新需求:增加一个类C,需要使用A和B的计算结果
class C {
private A a;
private B b;
public C(A a, B b) {
this.a = a;
this.b = b;
}
public int calculate() {
int resultA = a.calculate(); // 使用A的计算结果
int resultB = b.calculate(); // 使用B的计算结果
return resultA + resultB;
}
}
在上述示例中,类A和类B都有自己的计算逻辑,并且类C需要使用A和B的计算结果进行进一步的计算。如果在未来需求变更,需要修改类A或类B的计算逻辑,那么类C也需要相应地进行修改,否则可能导致计算结果错误。
这种情况下,类A和类B在不同的方向上发生变化,即它们的修改会影响到其他类(如类C)。这违反了单一职责原则(Single Responsibility Principle),应该考虑重构代码,将计算逻辑从类A和类B中抽取出来,形成一个独立的类,以避免异曲同工的类问题。
重构
要解决异曲同工的类问题,可以使用以下重构手法之一:抽取方法(Extract Method)和抽取类(Extract Class)。
-
抽取方法(Extract Method):
- 针对类A和类B中的计算逻辑,将其抽取为独立的方法。
- 在类A中创建一个新的方法,例如
calculateValue()
,将原来的计算逻辑放入其中。 - 在类B中也创建一个新的方法,例如
calculateValue()
,将原来的计算逻辑放入其中。 - 然后,类C可以直接调用这两个抽取出来的方法,而不需要再依赖类A和类B的具体实现。
-
抽取类(Extract Class):
- 创建一个新的类,例如
Calculator
,用于封装计算逻辑。 - 将类A和类B中的计算逻辑移动到
Calculator
类中的相应方法中。 - 类A和类B只需要引用
Calculator
类的实例,并调用相应的方法来获取计算结果。 - 类C也只需要引用
Calculator
类的实例,并调用相应的方法来获取A和B的计算结果。
- 创建一个新的类,例如
通过抽取方法或抽取类,我们将计算逻辑从原来的类中分离出来,形成一个独立的类或方法,以避免异曲同工的类问题。这样,当需要修改计算逻辑时,只需要修改独立的类或方法,而不会影响其他类的实现。这符合单一职责原则,使代码更加清晰、易于理解和维护。
请注意,具体的重构手法取决于代码的结构和需求的复杂程度。在实际应用中,可能需要结合其他重构手法或进行适当的调整,以满足具体情况的需求。
下面是使用抽取类(Extract Class)重构手法来解决异曲同工的类问题的代码示例:
// 抽取类示例
// 原始类A
class A {
private int x;
public void setX(int value) {
x = value;
}
public int calculate() {
// 计算逻辑
return x * 2;
}
}
// 原始类B
class B {
private int y;
public void setY(int value) {
y = value;
}
public int calculate() {
// 计算逻辑
return y * 3;
}
}
// 新建抽取的类Calculator
class Calculator {
public int calculateValue(int value, int multiplier) {
return value * multiplier;
}
}
// 新需求:增加一个类C,需要使用A和B的计算结果
class C {
private Calculator calculator;
private int resultA;
private int resultB;
public C(A a, B b) {
calculator = new Calculator();
resultA = calculator.calculateValue(a.getX(), 2); // 使用Calculator类的计算方法
resultB = calculator.calculateValue(b.getY(), 3); // 使用Calculator类的计算方法
}
public int calculate() {
return resultA + resultB;
}
}
在上述示例中,我们创建了一个新的类Calculator,用于封装计算逻辑。类A和类B的计算逻辑被移动到Calculator类中的calculateValue方法中。类C引用Calculator类的实例,并调用calculateValue方法来获取A和B的计算结果。
通过这种重构,我们将计算逻辑从原来的类A和类B中抽取出来,形成了一个独立的类Calculator。现在,当需要修改计算逻辑时,只需要修改Calculator类中的对应方法,而不会影响类A和类B的实现。这样,类C也不再依赖于类A和类B的具体实现,而是通过Calculator类来获取计算结果,解决了异曲同工的类问题。