我公司的遗留代码正遭受交换机外壳实例的普遍使用,其形式为:
if(object instanceof TypeA) {
TypeA typeA = (TypeA) object;
...
...
}
else if(object instanceof TypeB) {
TypeB typeB = (TypeB) object;
...
...
}
...
...
更糟糕的是,有问题的TypeX类实际上是在第三方库中找到的类的包装。
建议的在第三方类上使用访客设计模式和专用访客设计模式包装器的方法,如here (instanceof -> Visitor DP)和here (Visitor DP with 3rd party classes)所示,似乎是一个好方法。
但是,在建议采用这种方法的代码审查会议期间,每次重新构造开关壳体实例所需的样板代码的额外开销问题都导致该机制被拒绝。
我想解决这个持续存在的问题,并且正在考虑针对该问题的通用方法:
一个实用程序类,它将使用对被访问对象的通用引用来包装访问者设计模式。这个想法是只实现一次访问者实用工具类的通用核心,并在需要时为TypeX对象行为提供特定的实现-甚至希望通过面向对象的功能实现类的扩展来重用某些实现。
我的问题是-这里有人做过类似的事情吗?如果不是,您能否指出任何可能相关的利弊?
编辑:
过多的样板代码=专门为开关柜实例的每个实例实现访客设计模式。如果没有使用泛型实现访问者DP,这显然是多余的,并且将导致大量代码重复。
至于通用的访客DP实用程序,我想到的是:
首先,如here所示,对访客DP使用反射。
第二,泛型的以下用法(基于反射访问者):
public interface ReflectiveVisitor<GenericReturn,GenericMetaData>
{
public GenericReturn visit(Object o, GenericMetaData meta);
}
public interface ReflectiveVisitable<A,B>
{
public GenericReturn accept(Visitor visitor, GenericMetaData meta);
}
GenericReturn和GenericMetaData是接口,旨在为要实现的特定逻辑提供任何其他必需的元数据,并为访问者DP返回的返回类型提供多功能性。
提前致谢!
编辑:从instanceof重构到访客时的锅炉板编码:
为了执行具体实现的单个API调用,我必须处理的一个通用用例是instanceof切换框:
public class BoilerPlateExample
...
if(object instanceof TypeA) {
((TypeA) object).specificMethodTypeA(...)......;
}
else if(object instanceof TypeB) {
((TypeB) object).completeyDifferentTypeBMethod(...)......;
}
...
...
顾客设计处理这个吗?
public interface Visitor
{
// notice how I just binded my interface to a specific set of methods?
// this interface will have to be generic in order to avoid an influx of
// of dedicated interfaces
public void visit(TypeA typeA);
public void visit(TypeB typeB);
}
public interface Visitable
{
public void accept(Visitor visitor);
}
public class BoilerPlateExampleVisitable<T> implements Visitable
{
// This is basically a wrapper on the Types
private T typeX;
public BoilerPlateExampleVisitable (T typeX) {
this.typeX = typeX;
}
public void accept(Visitor visitor) {
visitor.visit(typeX);
}
}
public class BoilerPlateExampleVisitor implements Visitor
{
public void visit(TypeA typeA) {
typeA.specificMethodTypeA(...)......;
}
public void visit(TypeB typeB) {
typeB.completeyDifferentTypeBMethod(...)......;
}
}
public static final BoilerPlateExampleVisitor BOILER_PLATE_EXAMPLE_VISITOR = new BoilerPlateExampleVisitor();
public static void main(....) {
TypeA object = .....; // created by factory
BoilerPlateExampleVisitable boilerPlateVisitable = VisitableFactory.create(object); // created by dedicated factory, warning due to implicit generics
boilerPlateVisitable.accept(BOILER_PLATE_EXAMPLE_VISITOR);
}
最佳答案
TL; DR:假设您有N个类,每个类具有M个操作。仅当M可能增长并且N已经很大时才需要访问者模式。否则使用多态。
也许我会推开大门,因为您已经想到了这一点,但是这里有一些想法。
访客模式
在一般情况下,仅当您要添加新操作而不重构所有类时,才使用访问者模式。那就是M可能增长而N已经很大的时候。
对于每个新操作,您都会创建一个新访客。该访问者接受N个类,并为每个类处理操作:
public class NewOperationVisitor implements Visitor
{
public void visit(TypeA typeA) {
// apply my new operation to typeA
}
public void visit(TypeB typeB) {
// apply my new operation to typeB
}
...
}
因此,不必将新操作添加到所有N个类中,但是如果添加一个类,则必须重构每个访问者。
多态性
现在,如果M稳定,请避免访问者模式:使用多态。每个类都有一组定义明确的方法(每个操作大约一个)。如果添加一个类,只需定义该类的已知操作即可:
public class TypeX implements Operator
{
public void operation1() {
// pretty simple
}
public void operation2() {
// pretty simple
}
}
现在,如果添加操作,则必须重构每个类,但是添加类非常容易。
R. C. Martin在“清理代码”中对此折衷进行了解释(6.对象和数据结构,数据/对象反对称):
程序代码[此处:访问者]使得添加新的数据结构变得困难,因为所有功能都必须更改。 OO代码使添加新功能变得困难,因为所有类都必须更改。
你应该怎么做
如@radiodef注释中所述,请避免反射和其他技巧。这将比问题更糟。
明确区分您真正需要访客模式的地方和您不需要的地方。计算类和操作。我敢打赌,在大多数情况下,您不需要访客模式。 (您的经理可能是对的!)。如果在10%的情况下需要访问者模式,那么“样板代码的额外开销”可能是可以接受的。
由于您的几个
TypeX
类已经是包装器,因此您可能需要包装更好的包装。有时,其中一个从下到上环绕:“我的第三方类具有这些方法:我将包装需要的方法,而忘记其他方法。并且我将使用相同的名称来保持简单。”相反,您必须仔细定义TypeX
类应提供的服务。 (提示:查看您的if ... instanceof ...
正文)。然后再次包装第3方库以提供这些服务。的确:避免避免反思和其他技巧。
我该怎么办?
您在注释中要求提供一个伪代码,但是由于考虑到方法,过程或算法,我无法将其提供给您。
这是我在这种情况下要做的最少的逐步操作。
隔离方法中的每个“大
instanceof
开关”几乎是医学建议!之前:
public void someMethod() {
...
...
if(object instanceof TypeA) {
TypeA typeA = (TypeA) object;
...
...
}
else if(object instanceof TypeB) {
TypeB typeB = (TypeB) object;
...
...
}
...
...
}
后:
public void someMethod() {
...
...
this.whatYouDoInTheSwitch(object, some args);
...
...
}
和:
private void whatYouDoInTheSwitch(Object object, some args) {
if(object instanceof TypeA) {
TypeA typeA = (TypeA) object;
...
...
}
else if(object instanceof TypeB) {
TypeB typeB = (TypeB) object;
...
...
}
}
任何体面的IDE都会免费提供。
如果您遇到需要访客模式的情况
保留代码不变,但记录下来:
/** Needs fix: use Visitor Pattern, because... (growing set of operations, ...) */
private void whatYouDoInTheSwitch(Object object, some args) {
...
}
如果要使用多态
目标是从:
this.whatYouDoInTheSwitch(object, other args);
至:
object.whatYouDoInTheSwitch(this, other args);
您需要做一些重构:
答:在大开关中为每种情况创建一个方法。除了对象的类型外,所有这些方法应具有相同的签名:
private void whatYouDoInTheSwitch(Object object, some args) {
if(object instanceof TypeA) {
this.doIt((TypeA) object, some args);
}
else if(object instanceof TypeB) {
this.doIt((TypeB) object, some args);
}
}
同样,任何IDE都会免费提供。
B.用以下方法创建一个接口:
doIt(Caller caller, args);
其中
Caller
是您要重构的类的类型(包含大型开关的类)。C.通过将每个
TypeX
从doIt(TypeX objX, some args)
转换为doIt(Caller, some args)
方法,使每个TypeX
都实现此接口。基本上,这是一个简单的查找替换:将this
替换为caller
,将objX
替换为this
。但这可能会比其他时间花费更多时间。D.现在,您有:
private void whatYouDoInTheSwitch(Object object, some args) {
if(object instanceof TypeA) {
((TypeA) object).doIt(this, some args);
}
else if(object instanceof TypeB) {
((TypeB) object).doIt(this, some args);
}
}
这严格等于:
private void whatYouDoInTheSwitch(Object object, some args) {
if(object instanceof TypeA) {
object.doIt(this, some args);
}
else if(object instanceof TypeB) {
object.doIt(this, some args);
}
}
因为在运行时,JVM将为正确的类找到正确的方法(即多态!)。因此,这也等效于(如果object具有枚举类型之一):
private void whatYouDoInTheSwitch(Object object, some args) {
object.doIt(this, some args);
}
E.内联方法,您已经在
Caller
类中:public void someMethod() {
...
...
object.doIt(this, some args);
...
...
}
实际上,这只是一个草图,并且可能发生许多特殊情况。但是可以保证它相对较快和干净。只能对所有方法中的选定方法进行此操作。
如果可能,请务必在每个步骤之后测试代码。并确保为方法选择正确的名称。
关于java - 通过设计模式重构交换机机壳的旧实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49560288/