每当我看到一个行为在其参数类型上切换的方法时,我会立即首先考虑该方法是否实际上属于方法参数。例如,没有像这样的方法:
public void sort(List values) {
if (values instanceof LinkedList) {
// do efficient linked list sort
} else { // ArrayList
// do efficient array list sort
}
}
我会这样做:
values.sort();
// ...
class ArrayList {
public void sort() {
// do efficient array list sort
}
}
class LinkedList {
public void sort() {
// do efficient linked list sort
}
}
我们将行为转移到知道何时使用它的地方。我们创建了一个真正的抽象,您无需了解实现的类型或细节。对于您的情况,将此方法从原始类(我将称为O
)移动到type A
并在type中覆盖它可能更有意义B
。如果doIt
在某个对象上调用了该方法,请移动doIt
到A
并覆盖在不同的行为B
。如果doIt
最初调用的位置有数据位,或者在足够的地方使用了该方法,则可以保留原始方法并委托:
class O {
int x;
int y;
public void doIt(A a) {
a.doIt(this.x, this.y);
}
}
不过,我们可以进一步深入。让我们看一下使用布尔参数代替的建议,看看我们可以从中了解到您的同事的思维方式。他的建议是:
public void doIt(A a, boolean isTypeB) {
if (isTypeB) {
// do B stuff
} else {
// do A stuff
}
}
看起来很像 instanceof
我在第一个示例中使用的,除了我们正在外部化该检查。这意味着我们将不得不通过以下两种方式之一来调用它:
o.doIt(a, a instanceof B);
要么:
o.doIt(a, true); //or false
在第一种方式中,呼叫点不知道A
它的类型。因此,我们是否应该将布尔值一直传递下去?那真的是我们希望在整个代码库中使用的模式吗?如果我们需要考虑第三种情况会怎样?如果这是方法的调用方式,则应将其移至类型,然后让系统多态地为我们选择实现。
第二种方式,我们必须已经知道a
呼叫点的类型。通常这意味着我们要么在那儿创建实例,要么将这种类型的实例作为参数。在此处创建一个方法O
需要一个B
。编译器会知道选择哪种方法。当我们在经历这样的变化时,,至少比弄清楚抽象要好重复至少要弄清楚我们要去的方向。当然,我建议无论我们对此进行了什么更改,我们都不会真正完成。
我们需要更仔细地研究A
和之间的关系B
。通常,我们被告知,我们应该更倾向于继承而不是继承。并非在每种情况下都是如此,但是一旦我们B
从中挖掘继承自A
,这意味着在令人惊讶的多种情况下都是如此,这意味着我们认为B
是A
。B
应该像一样使用A
,除了它的工作方式略有不同。但是这些区别是什么?我们可以给差异起一个更具体的名字吗?这难道不B
就是一个A
,但真正A
有X
可能是A'
或B'
?如果这样做,我们的代码将是什么样?
如果我们移动的方法到A
如前面所说,我们可以注入的情况下X
进入A
,并委托该方法的X
:
class A {
X x;
A(X x) {
this.x = x;
}
public void doIt(int x, int y) {
x.doIt(x, y);
}
}
我们可以实现A'
和B'
并摆脱它B
。我们通过给可能更隐式的概念命名来改进了代码,并允许我们自己在运行时而不是编译时设置该行为。A
实际上也变得不太抽象。它不是在扩展的继承关系上,而是在委托的对象上调用方法。该对象是抽象的,但仅专注于实现上的差异。
不过,还有最后一件事要看。让我们回到您同事的建议。如果在所有呼叫站点上我们都明确知道A
我们拥有的类型,那么我们应该像这样进行呼叫:
B b = new B();
o.doIt(b, true);
我们在撰写时假设A
a的X
值为A'
or或B'
。但是也许这个假设是不正确的。这是A
和之间唯一重要的地方B
吗?如果是这样,那么也许我们可以采取稍微不同的方法。我们还有一个X
要么是A'
或B'
,但它不属于A
。只O.doIt
关心它,所以我们只将它传递给O.doIt
:
class O {
int x;
int y;
public void doIt(A a, X x) {
x.doIt(a, x, y);
}
}
现在我们的呼叫站点看起来像:
A a = new A();
o.doIt(a, new B'());
再次B
消失,抽象移到更集中的位置X
。但是,这一次A
通过了解更少而变得更加简单。它甚至不那么抽象。
减少代码库中的重复很重要,但是我们必须考虑为什么重复首先发生。复制可能是试图摆脱困境的更深层抽象的标志。