【问题标题】:Refactoring a rigid code to eliminate if-else statement of checking instanceOf重构刚性代码以消除检查 instanceOf 的 if-else 语句
【发布时间】:2019-01-10 08:12:58
【问题描述】:

大家好,

我有一个关于重构一段代码的问题。这些类的结构如下:

abstract class A
class A1 extends class A
class A2 extends class A
class A3 extends class A

abstract class AdditionalStuff {
    abstract void function(); 
}
class AdditionalStuffForA1 extends AdditionalStuff
class AdditionalStuffForA2 extends AdditionalStuff
class AdditionalStuffForA3 extends AdditionalStuff

class Implementation {
    List<A> aList; 
    .... //add A1, A2, A3 to aList

    AdditionalStuff aS;

    for (A instance: aList) {
        if(instance instanceOf A1)
            aS = new AdditionalStuffForA1();
        else if(instance instanceOf A2)
            aS = new AdditionalStuffForA2();
        else
            aS = new AdditionalStuffForA3();

        aS.function()
    }

}

我认为上面的代码是死板的,因为每次添加新的类An(例如A4和AdditionalStuffForA4)时,也必须修改if else语句。

我曾想过使用装饰器模式,但现在我认为装饰器模式无法解决我的问题。我想问一下,您能否建议我一种重构上述代码以消除使用 if-else 语句的方法? (请注意,我不能将 AdditionalStuff 的功能添加到 A 内部,因为它们的使用方式不同)

【问题讨论】:

  • AdditionalStuff 抽象类是否持有状态?
  • 嗨,阿里斯,感谢您的评论。我是面向对象编程语言的新手。你能告诉我保持状态是什么意思吗?这是否意味着 AdditionalStuff 包含字段?
  • 他的意思是:AdditionalStuff 有任何实例字段,还是只提供方法声明void function()(在这种情况下如果可以是接口)?
  • @Thomas:是的,它还包含许多其他字段和其他具体功能。我只是简化它。

标签: java inheritance design-patterns refactoring


【解决方案1】:

对此有多种方法。但是扩展现有实现的最简单的答案是这样的

abstract class A { 
    public abstract AdditionalStuff getAdditionalStuff();
}

class A1 extends class A { 
    @Override
    public AdditionalStuff getAdditionalStuff() {
       return new AdditionalStuffA1();
    }
}

class A2 extends class A { 
    @Override
    public AdditionalStuff getAdditionalStuff() {
       return new AdditionalStuffA2();
    }
}

class A3 extends class A { 
    @Override
    public AdditionalStuff getAdditionalStuff() {
       return new AdditionalStuffA3();
    }
}

abstract class AdditionalStuff {
    abstract void function(); 
}
class AdditionalStuffForA1 extends AdditionalStuff
class AdditionalStuffForA2 extends AdditionalStuff
class AdditionalStuffForA3 extends AdditionalStuff

class Implementation {
    List<A> aList; 
.... //add A1, A2, A3 to aList

    AdditionalStuff aS;

    for (A instance: aList) {
        aS = instance.getAdditionalStuff();
        aS.function()
    }

}

【讨论】:

  • 感谢乔治的帮助。但我不认为我可以修改 A、A1、A2、A3 类。因为有些场景只使用A、A1、A2、A3类,没有任何AdditionalStuff。
  • 然后你会为他们返回 null 。你会在调用 aS.function() 之前检查 aS 是否不为空
  • 如果您是这些类 A1、A2、A3、...的所有者,那么添加这个额外的方法不会有问题。任何没有对应 AdditionalStuff 的类,它们都会在被覆盖的方法中返回 null
  • @Lup 这回答了你的问题吗?
  • @Geoge Arokiam:嘿,乔治,非常感谢。我认为您的建议最适合代码实现。
【解决方案2】:

我能想到两种好方法。两者都是“工厂”设计模式的变体。

方法 1 是让 A 类负责创建 AdditionalStuff 子类型的实例。

abstract class A {
    abstract AdditionalStuff makeAdditionalStuff();
    ...
}

class A1 extends class A {
    AdditionalStuff makeAdditionalStuff() {
        return new AdditionalStuffForA1();
    ...
}

class Implementation {
    List<A> aList; 
    .... //add A1, A2, A3 to aList

    for (A instance: aList) {
        instance.getAdditionalStuff().function()
    }
}

方法2是将instanceOf测试抽象为工厂方法:

AdditionalStuff getAdditionalStuff(A instance) {
    if (instance instanceOf A1) {
        return new AdditionalStuffForA1();
    } else if(instance instanceOf A2) {
        return new AdditionalStuffForA2();
    } else {
        return new AdditionalStuffForA3();
    }
}

或更好:

AdditionalStuff getAdditionalStuff(A instance) {
    if (instance instanceOf A1) {
        return new AdditionalStuffForA1();
    } else if (instance instanceOf A2) {
        return new AdditionalStuffForA2();
    } else if (instance instanceOf A3) {
        return new AdditionalStuffForA3();
    } else {
        throw new RuntimeException("not implemented");
    }
}

您可以将if instanceof 测试替换为switch,并打开instance 的类名。但是对于少数子类,这样做并没有什么好处。 (海事组织)

【讨论】:

    【解决方案3】:

    如果不了解您的代码的实际含义,提出明智的建议有点困难,但我会研究工厂模式,即为每个 AdditionalStuff 子类提供一个工厂,在一些地图中注册并查看它通过实例的类向上:

    interface AdditionalStuffFactory {
      AdditionalStuff create();
    }
    
    class AdditionalStuffA1Factory implements AdditionalStuffFactory { 
      public AdditionalStuff create() {
        return new AdditionalStuffForA1();
      }
    }
    

    然后你在某处有一个Map&lt;Class&lt;? extends A&gt;, AdditionalStuffFactory&gt;,它会被工厂实例填充,例如registry.put(A1.class, new AdditionalStuffA1Factory() );

    您的循环将如下所示:

     for (A instance: aList) {
       AdditionalStuff aS = registry.get(instance.getClass()).create();
       aS.function();
     }
    

    当然还有进一步的优化,例如让“注册表”委托调用,从而得到类似registry.createFor(instance) 的东西,但你应该明白这一点。

    添加另一个 AdditionalStuff 将需要以下内容:

    • 创建附加类(你总是这样做)
    • 为该类创建工厂(除非您可以提供一些通用工厂)
    • 在您的注册表中注册新工厂(这可以通过某种查找机制完成,例如通过 CDI,但我不会在这里深入探讨)

    【讨论】:

      【解决方案4】:

      您可以尝试以下方法。

      如果没有必要,首先要避免使用继承。例如,您的 AdditionalStuff 只定义了一个合同 function 方法,它需要由您拥有的各种类实现。这可以更改为 interface 而不是 abstract 类。

      有了这个,只需更改列表以保存各种接口实现,例如:

      List<SomeInterface> list = new ArrayList<>;
      Collections.addAll(list, new ClassA(), new ClassB());
      

      然后在一行中遍历所有这些并执行:

      list.forEach(SomeInterface::function);
      

      这样你就可以避免复杂的继承(这里不需要),以及内省和其他一切。

      【讨论】:

        【解决方案5】:

        使用接口!

        在 Java 8 或更高版本中,接口可以有默认方法实现。

        你可以定义:

        public interface AdditionalStuff {
        
            void function(Object... params);
        }
        
        public interface AdditionalStuffForA1 extends AdditionalStuff {
        
            default void function(Object... params) {
                //do something with params here
                System.out.println("implementation for A1");
            }
        }
        
        public interface AdditionalStuffForA2 extends AdditionalStuff {
        
            default void function(Object... params) {
                //do something with params here
                System.out.println("implementation for A2");
            }
        }
        

        并使用适当的接口创建您的类:

        public abstract class A implements AdditionalStuff {
        
        }
        
        public class A1 extends A implements AdditionalStuffForA1 {
        
        }
        
        public class A2 extends A implements AdditionalStuffForA2 {
        
        }
        

        执行此操作:

        List<A> aList = new ArrayList<>();
        
        aList.add(new A1());
        aList.add(new A2());
        aList.add(new A2());
        
        for (A a : aList) {
             a.function(23, 45, "string...");
        }
        

        将打印:

        implementation for A1
        implementation for A2
        implementation for A2
        

        可以通过参数(Object ... params)控制附加行为

        【讨论】:

          【解决方案6】:

          经典的“教科书方法”是访问者模式。

          首先,你需要定义一个访问者界面:

          interface AVisitor<T> {
                T visitA1(A1 a1);
                T visitA2(A2 a2);
                T visitA3(A3 a3);
          }
          

          然后您需要通过抽象方法扩展您的A 类以接收访问者:

          abstract class A {
                ...
          
                public <T> abstract T recieve(AVisitor<T> visitor);
          }
          

          而您的 A1A2A3 类需要实现接收方法:

          class A1 extends A {
                ...
                public <T> T recieve(AVisitor<T> visitor) {
                    return visitor.visitA1(this);
                }
          }
          
          class A2 extends A {
                ...
                public <T> T recieve(AVisitor<T> visitor) {
                    return vistor.visitA2(this);
                }
          }
          
          class A3 extends A {
                ...
                public <T> T recieve(AVisitor<T> visitor) {
                    return visitor.visitA3(this);
                }
          }
          

          最后你必须在你的实现类中定义不同的visit方法:

          class Implementation implements AVisitor<AdditionalStuff> {
          
                void ... () {
                    List<A> aList;
                    ....
          
                    for (A instance : aList) {
                        AdditionalStuff aS = instance.recieve(this);
                        aS.function();
                    }
                }
          
                public AdditionalStuff visitA1(A1 a1) {
                    return new AdditionalStuffForA1();
                }
          
                public AdditionalStuff visitA2(A2 a2) {
                    return new AdditionalStuffForA2();
                }
          
                public AdditionalStuff visitA3(A3 a3) {
                    return new AdditionalStuffForA3();
                }
          }
          

          此方法的优点是您的 A、A1、A2 和 A3 类不需要任何关于这些AdditionalStuff-类的知识。缺点是当你需要第四个 A4 类时,你必须在接口中添加一个visitA4 方法......并且在每个实现这个AVisitor 接口的类中。 (编辑:但与您的 if (... instanceof ...) else if (... instanceof ...) 方法不同:访问者方法将保证您不会错过特殊的实例检查。如果您忘记添加用于处理 A4 类的实现,编译器会告诉您。 编辑:修复实现类的代码

          【讨论】:

            【解决方案7】:

            抽象或接口实现上的自己的方法,可以自己验证它,防止许多 if,在每个实现中只使用一个 if。在@Lup 的architecture 上添加此内容。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-04-11
              • 1970-01-01
              • 1970-01-01
              • 2017-10-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多