【问题标题】:Java design pattern for use different implementation of method with an abstract class as param使用抽象类作为参数的不同方法实现的 Java 设计模式
【发布时间】:2019-11-22 13:12:16
【问题描述】:

我在我的程序中遇到了受孕问题。

我有 4 个类(ChildA、ChildB、ChildC)扩展了一个父类(Parent)

我有一个类似的方法:

public void doInteraction(Parent a, Parent b){
a.doActionWith(b);
b.doActionWith(a);
}

我的 3 个子类和父类:

public abstract class Parent {
    public abstract void doActionWith(Parent other);
}

class ChildA extends Parent {

    @Override
    public void doActionWith(Parent other) {
        System.out.println("childA use doActionWith abstract");
    }

    public void doActionWith(ChildA chila) {
        System.out.println("childA use doActionWith childA");
    }

    public void doActionWith(ChildB chilb) {
        System.out.println("childA use doActionWith childB");
    }

    public void doActionWith(ChildC chilb) {
        System.out.println("childA use doActionWith childC");
    }

}

class ChildB extends Parent {

    @Override
    public void doActionWith(Parent other) {
        System.out.println("childB use doActionWith abstract");
    }

    public void doActionWith(ChildA childa) {
        System.out.println("childB use doActionWith childA");
    }

    public void doActionWith(ChildB chilb) {
        System.out.println("childB use doActionWith childB");
    }

    public void doActionWith(ChildC chilb) {
        System.out.println("childB use doActionWith childC");
    }

}

class ChildC extends Parent {

    @Override
    public void doActionWith(Parent other) {
        System.out.println("childB use doActionWith abstract");
    }

    public void doActionWith(ChildA childa) {
        System.out.println("childB use doActionWith childA");
    }

    public void doActionWith(ChildB chilb) {
        System.out.println("childB use doActionWith childB");
    }

    public void doActionWith(ChildC chilc) {
        System.out.println("childC use doActionWith childC");
    }

}

当我调用 a.doActionWith(b);在 doInteraction 方法中: 抽象方法总是在 jvm 知道 a 和 b 的真实类型时使用。

为什么java不使用带有参数的特定方法与参数的类型完全匹配?

我找到的唯一解决方案是将对象转换为他们的子类型。 但我需要用实例检查他们的类型。(想象一下,如果我有 20 个子类)。 我记得我的研究表明,一个例子揭示了一个不好的概念。

我尝试了一些类似的东西

    public abstract <C extends Parent> void doActionWith(C other);

但是一样。 我想象一个设计模式可以解决这个问题,但我没有找到。 也许我应该使用界面,但我不知道该怎么做。

感谢阅读。

法比恩。

【问题讨论】:

  • 访问者模式是解决它的一种方法。 IMO 很难调试,但有时它仍然是必要的。
  • 如果Parent 抽象在最重要的时候对您没有帮助,那么它的意义何在? (ABC 类之间的关系在您需要使用它们共同的 Parent 时没有帮助)

标签: java design-patterns polymorphism


【解决方案1】:

您可以使用visitor pattern:

public interface Parent {
    default void actWith(Parent parent) {
        parent.accept(this);
    }
    void accept(Parent parent);
    void doActionWith(ChildA childA);
    void doActionWith(ChildB childB);
    void doActionWith(ChildC childC);
}

public class ChildA implements Parent {
    @Override
    public void accept(Parent parent) {
        parent.doActionWith(this);
    }

    @Override
    public void doActionWith(ChildA childA) {
        System.out.println("childA use doActionWith childA");
    }

    @Override
    public void doActionWith(ChildB childB) {
        System.out.println("childA use doActionWith childB");
    }

    @Override
    public void doActionWith(ChildC childC) {
        System.out.println("childA use doActionWith childC");
    }
}

public class ChildB implements Parent {
    @Override
    public void accept(Parent parent) {
        parent.doActionWith(this);
    }

    @Override
    public void doActionWith(ChildA childA) {
        System.out.println("childB use doActionWith childA");
    }

    @Override
    public void doActionWith(ChildB childB) {
        System.out.println("childB use doActionWith childB");
    }

    @Override
    public void doActionWith(ChildC childC) {
        System.out.println("childB use doActionWith childC");
    }
}

public class ChildC implements Parent {
    @Override
    public void accept(Parent parent) {
        parent.doActionWith(this);
    }

    @Override
    public void doActionWith(ChildA childA) {
        System.out.println("childC use doActionWith childA");
    }

    @Override
    public void doActionWith(ChildB childB) {
        System.out.println("childC use doActionWith childB");
    }

    @Override
    public void doActionWith(ChildC childC) {
        System.out.println("childC use doActionWith childC");
    }
}
public static void interact(Parent a, Parent b) {
    a.actWith(b);
    b.actWith(a);
}

【讨论】:

    【解决方案2】:

    一句话,您的问题是您要使用double dispatch,而Java不直接支持。换句话说,您要根据两个参数的运行时类型来选择方法实现。

    因为 Java 不支持它,所以您必须模拟该机制。有不同的方法可以做到这一点。 Visitor pattern 是其中之一,但是如果您确实遇到任何具体子类都可以与任何其他具体子类一起使用的情况,那就不理想了。

    这是基于服务提供者接口概念的另一种解决方案。基本上,子类注册它们的方法实现,并且基于目标参数的调度由继承的方法显式地完成。这是一个假定最终子类的简化代码示例,但可以对其进行扩展以支持额外的多态灵活性。

    public abstract class Parent {
    
        protected IdentityHashMap<Class<?>, Consumer<Parent>> handlers 
            = new IdentityHashMap<>();
    
        public final void doActionWith(Parent p) {
            if( !handlers.containsKey(p.getClass())) 
                throw new UnsupportedOperationException();
    
            handlers.get(p.getClass()).accept(p);
        }
    }
    
    final class A extends Parent {
        public A() {
            handlers.put(A.class, a -> System.out.println("A works with A"));
            handlers.put(B.class, b -> System.out.println("A works with B"));
            handlers.put(C.class, c -> System.out.println("A works with C"));
        }
    }
    
    final class B extends Parent {
        public B() {
            handlers.put(A.class, a -> System.out.println("B works with A"));
            handlers.put(B.class, b -> System.out.println("B works with B"));
            handlers.put(C.class, c -> System.out.println("B works with C"));
        }
    }
    
    final class C extends Parent {
        public C() {
            handlers.put(A.class, a -> System.out.println("C works with A"));
            handlers.put(B.class, b -> System.out.println("C works with B"));
            handlers.put(C.class, c -> System.out.println("c works with C"));
        }
    }
    

    注意:在实际应用程序中,如果它可以引用各个子类的特定方法而无需向下转换,则编写处理程序代码将更容易和更安全。这可以通过在Parent中添加一个泛型方法来实现:

    protected <T extends Parent> void addHandler(Class<T> clazz, 
        Consumer<? super T> handler) {
        handlers.put(clazz, (Consumer<Parent>)handler);
    }
    

    【讨论】:

    • 如果我尝试让CC 的子类交互,这将失败。另外,我建议将protected &lt;T&gt; void addHandler(Class&lt;T&gt; clazz, Consumer&lt;? super T&gt; handler); 添加到Parent 类中。
    • 谢谢。我试图把它降到最低,但多一点错误检查不会有坏处。
    【解决方案3】:

    您同时依赖静态和动态绑定,但至少其中之一会不必要地损害您的代码:

    abstract Parent.doActionWith(Parent) 让你实现两件事:

    • 当在ChildAChildBChildC 中实现时,可以自定义行为以完全符合这些子类的需要。这是动态绑定的好处。但是,动态绑定基于调用方法的对象,而不是传递的参数的运行时类。这意味着编译器将始终在您的情况下选择 Parent.doActionWith(Parent),因为这是 Parent 类型知道的唯一一个。
    • 您可以调用doActionWith 方法,其参数类型为Parent 的任何子类。但是,由于您调用该方法的对象的静态(编译时)类型是Parent,因此选择的方法将始终是doActionWith(Parent);再说一遍,因为Parent 除了doActionWith(Parent) 知道没有其他签名。

    简要回顾一下:

    • 编译器决定调用方法的哪个签名。这是基于对象声明的数据类型。
      因为声明的参数类型是Parent,所以参数的运行时类是什么并不重要:调用的方法签名将是将Parent 声明为参数类型。
    • 运行时决定将执行编译时选择签名的哪个实现。你得到了这个,但它肯定是在子类中以Parent 作为参数类型的方法(ChildAChildBChildC,具体取决于调用该方法的对象的运行时类)。

    换句话说,如果您希望同时应用静态和动态绑定,您需要:

    1. 在抽象类中声明所有重载的方法
    2. doInteraction(Parent a, Parent b) 的签名更改为在参数列表中具有所有适用子类的组合。

    显然,这是不可取的。以下是我的看法:我会通过以下方式简化事情:

    1. 摆脱重载方法
    2. 覆盖每个子类中doActionWith(Parent)的相同签名的实现

    有了这个,关键是你如何覆盖doActionWith 方法。理想情况下,该方法不需要知道其参数的运行时类,这是一件好事

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      • 1970-01-01
      • 2015-01-03
      • 1970-01-01
      • 2022-11-20
      • 1970-01-01
      相关资源
      最近更新 更多