我发现了另一种方法,结果证明对我的情况非常有用。
就我而言,我需要创建一个扩展另一个类的新类,其中包括一个非常复杂的(旧版代码)protected final方法。由于复杂性,实际上不可能重构以使用合成,所以这就是我的想法。
假设我有以下内容:
abstract class Parent {
public abstract void implementMe();
protected final void doComplexStuff( /* a long parameter list */) {
// very complex legacy logic
}
}
class MyNewClass extends Parent {
@Override
public void implementMe() {
// custom stuff
doComplexStuff(/* a long parameter list */); // calling the parent
// some more custom stuff
}
}
这是我重新排列此代码的方式:
abstract class Parent {
public abstract void implementMe();
protected final void doComplexStuff( /* a long parameter list */) {
// very complex legacy logic
}
}
interface ComplexStuffExecutor {
void executeComplexStuff(/* a long parameter list, matching the one from doComplexStuff */);
}
class MyNewClass extends Parent {
private final ComplexStuffExecutor complexStuffExecutor;
MyNewClass() {
this.complexStuffExecutor = this::doComplexStuff;
}
MyNewClass(ComplexStuffExecutor complexStuffExecutor) {
this.complexStuffExecutor = complexStuffExecutor;
}
@Override
public void implementMe() {
// custom stuff
complexStuffExecutor.doComplexStuff(/* a long parameter list */); // either calling the parent or the injected ComplexStuffExecutor
// some more custom stuff
}
}
在出于生产目的创建MyNewClass实例时,我可以使用默认构造函数。
但是,在编写单元测试时,我将使用构造函数,在该构造函数中可以注入ComplexStuffExecutor,在那里提供模拟,并且仅从MyNewClass测试我的自定义逻辑,即:
class MyNewClassTest {
@Test
void testImplementMe() {
ComplexStuffExecutor complexStuffExecutor = Mockito.mock(ComplexStuffExecutor.class);
doNothing().when(complexStuffExecutor).executeComplexStuff(/* expected parameters */);
MyNewClass systemUnderTest = new MyNewClass(complexStuffExecutor);
// perform tests
}
}
乍看之下,似乎只是添加一些样板代码只是为了使代码可测试。但是,我也可以将其视为代码实际外观的指示器。也许有一天(有人会发现勇气和预算;)可以重构代码,例如使用ComplexStuffExecutor中doComplexStuff中的逻辑来实现Parent,将其注入MyNewClass中并摆脱继承。