【问题标题】:Java: If-else instanceof extended classesJava:扩展类的 if-else 实例
【发布时间】:2013-09-19 14:42:17
【问题描述】:

我有一个抽象类X 和一些扩展这个类的类,称它们为ABC

在其他一些类Y 中,我有一些方法调用取决于类的类型。 if-else 语句如下所示:

public class Y implements InterfaceY {

    public Y(){
    }

    public String doStuff (X x, Boolean flag) {
    String s = "";
    if (x instanceof A) {
        doStuff((A) x));
    } else if (x instanceof B) {
        doStuff((B) x));
    } else if (x instanceof C) {
        doStuff((C) x, flag); 
    } else {
        throw new Exeption();
    }
    return s;

    private String doStuff(A a) {
        return "";
    }

    private String doStuff(B b) {
        return "";
    }

    private String doStuff(C c, Boolean flag) {
        return "";
    }
}

请注意,所有方法都具有相同的名称 (doStuff()),但根据类(有时是标志)调用该方法的不同方法实现。当然,一旦从 X 扩展的类增加,这看起来很可怕并且变得非常复杂。

有什么方法可以让我创建一个中间接口(或其他接口)来处理大部分(或所有)if-else 语句?

【问题讨论】:

  • 在你的if中,X应该是x吗?
  • @jonhopkins,确实如此。我会解决的。
  • 通常,在面向对象的上下文中,如果您使用过多的instanceof,您的设计可能是错误的。当然这就是你在这里发帖的原因:)
  • 我想在我们给出正确答案之前,我们需要知道:(1)你能在X中添加方法吗? (2) 可以给ABC添加方法吗?如果它们都是您自己程序的一部分,那么这两个问题的答案都应该是肯定的;但有时我们不得不面对我们无法修改的库类的这些问题。
  • @ajb,我可以更改任何我想要的课程。但是由于这只是一个巨大产品的一小部分,改变抽象/超级/接口类中的某些内容意味着重构一大堆代码。这就是为什么对我来说最好的解决方案是一些中级课程。

标签: java interface polymorphism abstract-class


【解决方案1】:

先把这些方法从这里取出来,分别放到A、B、C类中,实现X接口。

private String doStuff(A a) {
    return "";
}

private String doStuff(B b) {
    return "";
}

private String doStuff(C c, Boolean flag) {
    return "";
}

然后:

if (x instanceof A) {
    doStuff((A) x));
} else if (x instanceof B) {
    doStuff((B) x));
} else if (x instanceof C) {
    doStuff((C) x, flag); 

可以只是x.doStuff();(你甚至不必传递A、B、C,因为那将是方法内的this。你必须更具体地根据你的代码。例如,其他 2 个doStuff 方法也可以接受该标志,但忽略它)

【讨论】:

  • 你不能做x.doStuff(),除非你也在X中定义了doStuff。并且该方法必须在所有类(XABC)中采用flag 参数,因为配置文件必须相同才能使用多态性工作。最后,虽然我认为这是“正确”的解决方案,但我们不知道 OP 是否有能力修改X。有时你会在别人的图书馆里遇到这样的问题。
  • @ajb 是的,显然我在谈论 x 作为具有 doStuff 的接口。我还谈到了采用标志参数。考虑到 OP 正在(或多或少)寻找代码的重构,我认为一切都准备好了。
  • 对不起,当你说他们“可以”接受一个标志时,我解释为它是可选的。不是。
  • @ajb by "could",我的意思是这是解决问题的潜在方法。我立即想到的唯一一个,但可能还有其他人。具体到这个问题,您可能能够推断出一些关于标志的信息。我并不是说没有问题,如果你愿意,你可以这样做。
  • @Cruncher 我会试试这个解决方案,这似乎是个好主意。但是由于我的示例只是一个巨大产品的一小部分,这可能会导致重大的重构,但也可能不会;我将不得不接受这一点。
【解决方案2】:

如何实现Handler 接口然后按支持的类型映射它:

public interface Handler<T extends X>{
     Class<T> supportedClass;

     void doStuff(T value, Object...args);
}


public class Y implements InterfaceY {

       private Map<Class<?>, Handler<?>> handlers;

      public Y(List<Handler<?>> handlers){
          // populate map
      }


      public void process(X value){
           handler.get(value.getClass).doStuff(X, ...);
           // you would have to figure out how to determine when other values are needed
      }
}

【讨论】:

    【解决方案3】:

    一些double dispatch呢?

    class X {
        public String letYdoStuff(Y y, Boolean flag) {
            return y.doStuff(this, flag);
        }
    
        public static void main(String [] args) {
            //X x = new A();
            X x = new B();
            Y y = new Y();
    
            y.doStuff(x, false);
        }
    
        public X getThis() {
            return this;
        }
    }
    
    class A extends X {
        public String letYdoStuff(Y y, Boolean flag) {
            return y.doStuff(this, flag);
        }
    }
    class B extends X {
        public String letYdoStuff(Y y, Boolean flag) {
            return y.doStuff(this, flag);
        }
    }
    class C extends X {
        public String letYdoStuff(Y y, Boolean flag) {
            return y.doStuff(this, flag);
        }
    }
    
    class Y {
        public Y(){
        }
    
        public String doStuff (X x, Boolean flag) {
           String s = "";
    
           return x.letYdoStuff(this, flag);
       }
    
       public String doStuff(A a, Boolean flag) {
           System.out.println("in A");
           return "";
       }
    
       public String doStuff(B b, Boolean flag) {
           System.out.println("in B");
           return "";
       }
    
       public String doStuff(C c, Boolean flag) {
           System.out.println("in C");
           return "";
       }
    }
    

    【讨论】:

    • 这行不通。这里没有任何东西会导致任何多态性发生。结果就是 letYdoStuff 和第一个 Y.doStuff 会递归调用对方,直到栈溢出。
    • +1 这似乎是正确的解决方案... -1 但不会按当前给出的方式按预期工作。
    • @ajb 你是对的,它的方式是行不通的。编辑以提供完整的示例。
    【解决方案4】:

    方法 1

    使用state pattern。它可以解决您的问题并消除ifs 和elses。

    这里是java example

    状态模式将方法调用委托给实现相同接口但行为不同的对象。

    状态模式示例:

    public class StatePatternExample {
        public static void main(String[] args) {
            Girlfriend anna = new Girlfriend();
                                // OUTPUT
            anna.kiss();        // *happy*
            anna.greet();       // Hey, honey!
            anna.provoke();     // :@
            anna.greet();       // Leave me alone!
            anna.kiss();        // ...
            anna.greet();       // Hey, honey!
        }
    }
    
    interface GirlfriendInteraction extends GirlfriendMood {
        public void changeMood(GirlfriendMood mood);
    }
    
    class Girlfriend implements GirlfriendInteraction {
        private GirlfriendMood mood = new Normal(this);
    
        public void provoke() {
            mood.provoke();
        }
        public void kiss() {
            mood.kiss();
        }
        public void greet() {
            mood.greet();
        }
        public void changeMood(GirlfriendMood mood) {
            this.mood = mood;
        }
    }
    
    interface GirlfriendMood {
        public void provoke();
        public void kiss();
        public void greet();
    }
    
    class Angry implements GirlfriendMood {
        private final GirlfriendInteraction context;
    
        Angry(GirlfriendInteraction context) { // more parameters, flags, etc. possible
            this.context = context;
        }
        public void provoke() {
            System.out.println("I hate you!");
        }
        public void kiss() {
            System.out.println("...");
            context.changeMood(new Normal(context));
        }
        public void greet() {
            System.out.println("Leave me alone!");
        }
    }
    
    class Normal implements GirlfriendMood {
        private final GirlfriendInteraction context;
    
        Normal(GirlfriendInteraction context) {
            this.context = context;
        }
    
        public void provoke() {
            System.out.println(":@");
            context.changeMood(new Angry(context));
        }
    
        public void kiss() {
            System.out.println("*happy*");
        }
    
        public void greet() {
            System.out.println("Hey, honey!");
        }
    }
    

    如您所见,Girlfriend 类没有 ifs 和 elses。它看起来很干净。

    Girlfriend 类对应您的abstract class XNormalAngry 类对应ABC

    您的班级 Y 然后直接委托给 X 而不检查任何情况。

    方法 2

    使用command pattern。然后,您可以将命令对象交给Ys doStuff() 方法并执行它。

    【讨论】:

    • 请说明state 模式将用于解决此问题的方式。
    • 不知道这将如何解决问题。您已经描述了状态模式,但没有描述如何使用它来解决这个问题。
    • Visitor 会更喜欢它。这似乎是错误的方法。
    • @Dirk 他不想外包昂贵的操作或代码本身。他希望根据状态(在本例中为方法的状态)做出不同的反应。因此他应该在他的设计中加入状态模式。
    • @JohnB 画了一些相似之处,这清楚地说明了为什么我的回答有助于 OP 消除 ifs 和 elses - 这是他的问题。
    【解决方案5】:

    这可能是个难题。我认为 Cruncher 的解决方案,添加 doStuff 到 X 并在 ABC 中覆盖它是最简单的 合适的时候最好的解决方案。然而,并不总是 合适,因为Single responsibility principle。 (我认为这是正确的术语。如果我得到一些,我很抱歉 术语错误,我并不完全了解所有术语。)

    这个想法是,如果有的话,您不一定要 doStuffXX 的目的无关。如果 XY 是 相同的“团队”,即他们都被设置为服务于一个目的 特定的应用程序,那么它可能没问题。

    但是假设你有一个抽象的Shape 类,它有子类 CircleSquareUndecagonRandomBlob等会有 属于Shape 类的一些方法对 任何使用Shape 类的应用程序。但现在说你是 编写一个使用其中一些形状的游戏,并且你想要一个 确定形状获取时发生的情况的多态操作 被飞猴吃掉。您不想添加摘要 computeEatenByFlyingMonkey 方法到您的 Shape 类,即使 类是你自己创造的,而不是在别人的图书馆里, 因为这对于一个可能的类来说太具体了 通常用于除此游戏之外的其他目的。

    我可以想到几种方法来解决这个问题。

    如果不适合(或不可能)将doStuff 添加到X,但是 如果ABC 与您的应用程序联系更紧密,那么 给他们添加doStuff是合适的,你可以添加另一个 类:

    public abstract class XWithStuff extends X {
        // repeat any constructors in X, making them all be just
        // calls to super(...)
        public abstract void doStuff (Boolean flag);
    }
    
    public class A extends XWithStuff {
        @Override
        public void doStuff (Boolean flag) { ... }
    }
    

    以此类推。 (XWithStuff 只是一个例子 姓名;在现实生活中,一个包含“X”和一些参考的名称 应用程序或目的可能更好。)(P.S.我不知道 为什么你使用 Boolean 而不是 boolean 但我要离开它 以防万一有充分的理由。)

    如果将doStuff添加到A也不合适或不可能, BC,这是一个可能的解决方案:

    public interface StuffInterface {
        public void doStuff (Boolean flag);
    }
    
    public class AWithStuff extends A implements StuffInterface {
        @Override
        public void doStuff (Boolean flag) { ... }
    }
    

    然后在您的程序中创建 AWithStuff 类的对象 A 等。在 X 上调用 doStuff

    void doStuff (X x, Boolean flag) {
        if (x instanceof StuffInterface)  {
            ((StuffInterface) x).doStuff (flag);
        } else {
            throw new IllegalArgumentException ();
        }
    }
    

    如果这不是一个选项,你必须直接处理AB, 等等,并且您不能将doStuff 添加到这些类中,然后是任何解决方案 会有点hacky。如果你不想使用if-then-else,你 可以查看visitor pattern,或者您可以想象创建 一个HashMap&lt;Class&lt;?&gt;,Interf&gt;,它将映射A.classB.class, 等等,到一些调用正确doStuff 的接口对象。但 我还没有弄清楚细节。 (实际上,“访问者模式”可能不合适,除非你有某种由 X 类型的对象组成的复杂结构。)

    【讨论】:

      【解决方案6】:

      分离一个DoStuffOperation,创建相关工厂并使用它们。

        public interface DoStuffOperation<T> {
          String doStuff(T x);
        }
        public class ADoStuffImpl implements DoStuffOperation<A> {
          public String doStuff(A x) {
            return "doStuff<A>";
          }
        }
        public class ADoStuffWithFlagImpl implements DoStuffOperation<A> {
          public String doStuff(A x) {
            return "doStuffWithFlag<A>";
          }
        }
        public class DoStuffImplFactory {
          public final static <T extends X> DoStuffOperation<X> getDoStuff(Class<T> xClass,boolean flag)  {
            DoStuffOperation<X> impl = null;
      
            if(xClass.equals(A.class))
            {
              if(flag)
                impl = (DoStuffOperation)new ADoStuffWithFlagImpl();
              else
                impl = (DoStuffOperation)new ADoStuffImpl();
              }
            }
            return impl;
          }
        }
      
        public class Y implements InterfaceY {
          public String doStuff (X x, Boolean flag) {
            return DoStuffImplFactory.getDoStuff(x.getClass(),flag).doStuff(x);
        }
      }
      

      通过这种方式,您不必重构对Y.doStuff()X 和派生类的调用。 您根本无法删除某种instanceof 来决定使用doStuff() 的哪个实现,除非X 类实现了DoStuffCreator 接口,例如:

      interface DoStuffCreator {
        DoStuffOperation getDoStuffOperation(boolean flag);
      }
      

      XA 是您的课程。您也可以使用反射或其他自动方式(外部属性文件等)构建。

      【讨论】:

        猜你喜欢
        • 2019-08-19
        • 1970-01-01
        • 1970-01-01
        • 2010-12-14
        • 2011-03-22
        • 1970-01-01
        • 1970-01-01
        • 2021-02-19
        • 2013-07-04
        相关资源
        最近更新 更多