这是一种众所周知的重构模式,可以将条件条件替换为多态性。 如果您不熟悉该模式,可以在此处查看 。 但是,一旦该类中有多个条件检查所基于的字段,该基本解决方案便会开始崩溃。 我们将研究一些有关如何使用这些可能性的想法。
有很多方法可以解决,因此我们将从最简单到最困难的工作,始终使用简单的示例来尽可能减少混乱。 那么,最简单的情况是什么? 看一看:
public class ClassWithConditionals
{private boolean conditional1;private EnumeratedType conditional2;public ClassWithConditionals(boolean cond1, EnumeratedType cond2){conditional1 = cond1;conditional2 = cond2;}public void method1(){if(conditional1){//do something}else{//do something else}}public void method2(){switch(conditional2){case CASE1://do somethingbreak;case CASE2://do something elsebreak;case CASE3://do something entirely differentbreak;}}
}enum EnumeratedType
{CASE1,CASE2,CASE3
}
因此,在此示例中,我们有ClassWithConditionals
在其方法中使用的两个不同字段。 在一个合适的示例中,您将假设不仅使用给定的两个方法,还可以使用更多的方法,但是对于该示例,我们仅需要两个方法。 如果每个条件只使用一种方法,那么您就不必担心,因为维护成本仍然很低。 但是,只要执行这种条件检查的方法数量增加,就应该考虑进行这种重构。
通常,如果您要遵循用多态替换条件,您将最终得到六个类来解决此问题: boolean
和enum
每个组合都有一个。 取而代之的是,我们将使用合成。
那么,第一步是什么? 首先,我们可能应该使用enum
类型。 enum
可以有自己的方法,可以以允许其根据特定enum
做不同事情的方式定义这些方法。 因此,让我们将
更改如下: enum
eratedType
enum EnumeratedType
{CASE1(){public void doSomething(){//do something}},CASE2(){public void doSomething(){//do something else}},CASE3(){public void doSomething(){//do something entirely different}};public abstract void doSomething();
}
现在, method2
仅需要将自身委托给conditional2.doSomething()
。
现在让我们修复boolean
。 我们创建一个接口,该接口对所有非封装类(为了测试起见,可能是包)都私有,称为Conditional1
。 然后我们用True
和False
对其进行子类化。 这是代码:
interface Conditional1
{static Conditional1 TRUE = new True();static Conditional1 FALSE = new False();void doSomething();
}class True implements Conditional1
{public void doSomething(){//do something}
}class False implements Conditional1
{public void doSomething(){//do something else}
}
我决定在接口上创建TRUE
和FALSE
实例的原因很简单:它们都是无状态类,这意味着每个实例都具有多个实例是没有意义的。 它还允许我们像enum
一样调用它们。
同样,现在主要类仅需要委托。 这是固定类现在的样子
public class ClassWithConditionals
{public static ClassWithConditionals with(boolean cond1, EnumeratedType cond2){Conditional1 conditional1;if(cond1)conditional1 = Conditional1.TRUE;elseconditional1 = Conditional1.FALSE;return new ClassWithConditionals(conditional1, cond2);}private Conditional1 conditional1;private EnumeratedType conditional2;ClassWithConditionals(Conditional1 cond1, EnumeratedType cond2){this.conditional1 = cond1;this.conditional2 = cond2;}public void method1(){conditional1.doSomething();}public void method2(){conditional2.doSomething();}
}
这里有些奇怪。 我们已经用另一个替换了一个条件。 我们的构造函数足以接受一个Conditional1
,但是我们有一个静态工厂方法,该方法仍然采用boolean
并对此进行条件检查。
考虑到从技术上讲,除非有多种方法进行检查,否则我们不会重构此代码,因此,我们进行了许多检查并将其归为一类。 同样,在工厂中通常认为有条件的还可以,将所有检查强制到一个位置,并允许多态性从那里接管。 您不必使用静态工厂方法作为工厂,但是它是最快速,最简单的实时设置方法。 允许调用新ClassWithConditionals
对象的创建代码的代码仍然可以按过去的方式传递boolean
的另一个好处是,它允许我们封装和隐藏基于条件的类的实现细节。 新ClassWithConditionals
创建者无需担心创建Conditional1
对象,甚至无需知道它的存在。
我们仍然希望构造函数接受Conditional1
对象,这有两个原因:1)它将条件逻辑保存在工厂中,而不是构造函数中,后者是首选,并且2)它允许我们传递Conditional1
对象的测试双精度。
实际上,由于第2点,我们应该经常考虑将其enum
转换为更类似于Conditional1
的静态实例。 这将允许您更多地使用测试双打。 它还将有助于继承或通过组合进行扩展,这将在稍后进行讨论。
可以想到很多小变化。 首先,条件boolean
不需要boolean
或enum
。 可以有一组基于数字或其他条件的条件表达式。 通常,在这些情况下,我们用一个小的辅助方法来代替支票以使其更清晰,即if(numberOfPeople <= 3)...
变成if(isACrowd(numberOfPeople))...
我们可以更进一步,并创建通过工厂创建的GroupsOfPeople
层次结构。 如果工厂的值为1,则返回SinglePerson
; 给定2,则返回Company
对象; 给定3或更多,它将返回一个Crowd
对象。 这些对象中的每个对象都有其自己的方法,这样可以帮助减少原始类中的代码量。
另一个变化是当不同的条件字段集分层在一起时( if(condition1 && condition2)
等)。 为了解决这个问题,您可以走继承路线并创建爆炸的类以覆盖所有组合。 另一个选择是用一个小的层次结构替换一个条件对象,该层次结构接受委托方法中的其他条件对象,在该方法中,它仍然具有一些条件代码,但是可读性更强。 例如,您可以将使用两个布尔值的类转换为如下形式:
public class ClassWithConditionals
{public static ClassWithConditionals with(boolean condition1, boolean condition2){Conditional1 cond1;if(condition1)cond1 = Conditional1.TRUE;elsecond1 = Conditional1.FALSE;return new ClassWithConditionals(cond1, condition2);}private Conditional1 condition1;private boolean condition2;ClassWithConditionals(Conditional1 condition1, boolean condition2){this.condition1 = condition1;this.condition2 = condition2;}public void method(){condition1.method(condition2);}
}interface Conditional1
{static Conditional1 TRUE = new True();static Conditional1 FALSE = new False();void method(boolean condition2);
}class True implements Conditional1
{public void method(boolean condition2){if(condition2){//do something}else{//do something else}}
}class False implements Conditional1
{public void method(boolean condition2){if(!condition2){//do something really different}//and do this}
}
Condition1
的method
接受一个布尔值,然后使用它进行更多的条件处理。
另外,如果所有逻辑都允许,则可以创建一组类来替换其中一个条件,然后让其创建代码接受其他条件以决定其创建的一部分。 例如:
public class ClassWithConditionals
{public static ClassWithConditionals from(boolean condition1, boolean condition2){return new ClassWithConditionals(Conditional1.from(condition1, condition2));}private Conditional1 conditionOne;ClassWithConditionals(Conditional1 conditionOne){this.conditionOne = conditionOne;}public int method(){return conditionOne.method() * -6;}
}interface Conditional1
{static Conditional1 from(boolean condition1, boolean condition2){if(condition1)return True.with(condition2);elsereturn False.with(condition2);}int method();
}class True implements Conditional1
{public static True with(boolean condition2){if(condition2)return new True(5);elsereturn new True(13);}private int secondary;public True(int secondary){this.secondary = secondary;}public int method(){return 2 * secondary;}
}class False implements Conditional1
{public static False with(boolean condition2){if(condition2)return new False((x, y) -> x - y, 31);elsereturn new False((x, y) -> x * y, 61);}private final BinaryOperator operation;private final int secondary;public False(BinaryOperator operation, int secondary){this.operation = operation;this.secondary = secondary;}public int method(){return operation.apply(4, secondary);}
}
对于True
,第二个条件决定method
计算中的次要数字。 在False
,它会执行此操作并找出要应用于计算的运算符。
我不确定是否会发生类似的事情,但是如果确实发生了,您现在知道了一种解决方法。
总的来说,这整套重构从本质上将代码从单个类更改为Facade。 它需要大量的新类,并且使您可以使用与以前的单个类几乎完全相同的方式来使用整个工具包和kaboodle,唯一真正的区别是调用静态工厂方法而不是构造函数。
这不是特别重要; 我只是想向您指出。
希望您不必担心继承或“通过合成扩展”此类。 但是您可能必须这样做。
如果您要编写的扩展仅真正改变了条件对象的功能,则可以简单地编写一个新的Factory,为构造函数提供一组新的条件对象。 例如,您可以将此静态工厂方法添加到ClassWithConditionals
的最新版本中:
public static ClassWithConditionals different(int value)
{return new ClassWithConditionals(new SimpleConditional1(value));
}
与SimpleConditional1
看起来像这样
class SimpleConditional1 implements Conditional1
{private final int value;public SimpleConditional1(int value){this.value = value;}public int method(){return value;}
}
除此之外,您还可以提供原始需要的任何条件对象,并覆盖您需要覆盖的所有方法。
因此,这就是我用一个更多的OO选项替换多个条件的结果。 您还有其他方法可以做到吗? 您是否有一个无法奏效的示例,您希望我对此加以抨击? 让我知道,我会看看可以做什么。
谢谢阅读。
翻译自: https://www.javacodegeeks.com/2015/01/replacing-multiple-conditionals-with-polymorphism-and-composition.html