【问题标题】:Calling a method of extended class while initialising it as interface [duplicate]在将扩展类初始化为接口时调用扩展类的方法[重复]
【发布时间】:2016-10-09 14:09:37
【问题描述】:
public interface Foo {
}

public class ExtendedFoo implements Foo {
    public void myMethod() {
        System.out.println(1);
    }
}

public class AnotherExtendedFoo implements Foo {
    @Override
    public String toString() {
        return "hello world"
    }
}

public class UnknownImplementedFoo {
    public final Foo foo; // can be either ExtendedFoo OR AnotherExtendedFoo

    public UnknownImplementedFoo(ExtendedFoo f) {
        this.foo = f;
    }

    public UnknownImplementedFoo(AnotherExtendedFoo f) {
        this.foo = f;
    }
}

...
public void myTest() {
    ExtendedFoo f1 = new ExtendedFoo();
    AnotherExtendedFoo f2 = new AnotherExtendedFoo();

    UnknownImplementedFoo ifoo1 = new UnknownImplementedFoo(f1);
    System.out.println(ifoo1.foo.myMethod()); // can't access myMethod!

    System.out.println(ifoo1.type); // prints ExtendedFoo@21599f38
                                    // it knows which type of Foo it is
                                    // so why can't it call its custom methods?

    UnknownImplementedFoo ifoo2 = new UnknownImplementedFoo(f2);
    System.out.println(ifoo2); // prints hello world

}
...

问题显示在最后(myTest 方法),我无法访问扩展接口的类的属性/方法。

有什么解决方法吗?

也就是说,我希望 UnknownImplementedFoo 采用任何实现 Foo 的类(即不仅仅是这两个),同时仍然能够访问公共属性/方法。

【问题讨论】:

  • 请注意,您的最后一条语句没有输出“hello world”

标签: java


【解决方案1】:

警惕The Liskov Substitution Principle。接口是对象的公开声明契约,应该能够在同一段代码中交换接口的不同实现,而不会破坏程序/令人惊讶的副作用。

Java 使想要调用未在接口上声明的方法的情况,但只有其中一个实现变得困难,特别是因为想要这样做标志着一个 OO 设计问题。因此,我强烈鼓励您重新设计您正在使用的类,以便在接口上声明所有公开需要的方法。

也就是说,如果您真的必须解决问题(并且在某些情况下可能需要),那么这里可以采取一些方法。

  • 方法一:使用 instanceof 和强制转换 (警告:这会导致代码脆弱,并且经常被 OO 纯粹主义者和负责维护代码的人所反对)

    if ( x instanceof ExtendedFoo ) { ((ExtendedFoo) x).myMethod(); }

  • 接近二Double Dispatch/Visitor Pattern/Command Pattern (警告:这会导致代码需要花费更多精力来遵循和理解,并且它仅在希望每个类具有不同行为时才有效,但它确实具有比使用 instanceof 和强制转换提供更多编译时安全性的优势。技术,但不要过度使用)

    在接口Foo中添加一个方法'public void visit(Visitor v)'。

    访客应该这样声明:

    公共界面访客{ 公共无效访问ExtendedFoo(ExtendedFoo f); public void visitAnotherExtendedFoo(AnotherExtendedFoo f);
    }

    visit(Visitor) 的实现随后被编码以调用 Visitor 上的适当方法。

  • 方法三Java 8 'default' keyword

    Java 8 现在允许我们向接口上的方法添加实现。这有助于我们将方法添加到接口上,而不必强制为该接口的每个实现添加自定义实现。当方法适合接口的 OO 设计时非常有用,但不要试图根据standard OO advice 添加概念上不属于接口的方法。

  • 方法四:反思

    使用 Java 反射在运行时调用方法。这类似于使用 instanceof 和强制转换,主要区别在于一个在编译时执行检查,另一个在运行时执行。

再一次,只有在您已经排除、双重排除和三重排除后才使用上述任何一种技术,并通过同行评审对 OO 设计进行更改(特别是将方法添加到原始界面和/或挑战目的)以及接口的作用)。

【讨论】:

  • 说实话,这并不能解决问题。尽管它被标记为已回答。他要求一种调用此方法的方法,但这并没有提供解决方案。 Java中有一种方法可以做到他想要的,而无需进行任何转换。我不确定他为什么不应该使用它。
【解决方案2】:

你真的在与像 java 这样的强类型语言的工作方式作斗争。

接口的目的之一是通过捕获不同类之间的共同功能来实现多态性。

您正在寻求一种方法来访问一组共享一个通用标记(空)接口的类的非通用特性。

您可以使用反射来询问类实例以获取类型信息,甚至调用方法,但我不知道这将如何为您提供可行的解决方案。

您想研究解决这种情况的四种模式之一。一个例子是命令模式,它抽象了客户端知道在对象上调用了哪些方法的需要:

Command 类包含以下的一些子集:一个对象,一个 要应用于对象的方法,以及要传递的参数 应用该方法时。命令的“执行”方法然后导致 拼凑在一起。

【讨论】:

  • 我明白了。我猜访客模式也可以工作。与命令模式相比,使用访问者模式有什么优点/缺点吗? @RobertMoskal
  • 我的 java 有许多模式可以解决这类问题。我可以清楚地看到带有命令的解决方案,我对访客没有任何想法。不是说没有。
  • Visitor、Command、Double Dispatch 都非常相似.. 用于稍微不同的情况。这实际上取决于您要建模的内容,这是从问题中抽象出来的。
  • Visitor 在接口本身进行调度时非常有用,比如在遍历图时。我们可以为每个节点添加一个访问方法。命令模式将调度知识与原始类分开,这对于分离数据和逻辑非常有用……一个例子是编写 JVM 字节码解释器。
【解决方案3】:

你做一件事给 Foo 接口做一个函数签名。

public interface Foo{
   public void myMethod();
}

【讨论】:

  • 但这意味着必须在所有扩展 Foo 的类中实现 myMethod,这不是我们想要的 =)。
  • @emihir0 这正是它的意思,是的。我怀疑您不想这样做的原因归结为建模挑战,因为我们在问题中没有足够的细节,所以很难为您提供建议。
  • 但是根据我在这里的理解,您正在向上转换,在这种情况下,您只有我告诉过的一种选择。 Java 强制它以避免错误。假设 foo=f2 并且我们调用 foo.myMethod();这将给出运行时异常。为了避免这个 Java 强制执行这个规则
【解决方案4】:

使用 Java 泛型获得类型安全的版本。

public class UnknownImplementedFoo<FooType extends Foo> {
    public final FooType foo; // can be either ExtendedFoo OR AnotherExtendedFoo

    public UnknownImplementedFoo(FooType f) {
        this.foo = f;
    }
}

好像理解的有点问题。不知道为什么有 2 票没有评论。

正确使用:

ExtendedFoo f1 = new ExtendedFoo();
AnotherExtendedFoo f2 = new AnotherExtendedFoo();

UnknownImplementedFoo<AnotherExtendedFoo> ifoo1 = new UnknownImplementedFoo<>(f2);
// syntax error
// ifoo1.foo.myMethod()); 

UnknownImplementedFoo<ExtendedFoo> ifoo2 = new UnknownImplementedFoo<>(f1);
// valid
ifoo2.foo.myMethod(); 

【讨论】:

  • 添加了正确的类型安全用法,因为它似乎不清楚(downvotes ~~)
  • 这个答案可以通过包括这种方法的优点/缺点来改进。我将其视为将方法添加到接口解决方案的一种变体,它以可读性为代价来避免在多个地方编写方法。 Java 8 提供了更简洁的默认方法。这两种方法都适用于不同的情况。我投了赞成票。
  • 您试图用模式回答问题。这很好,但不准确。由于模式需要转换为语言甚至是特定于框架的解决方案。问题很简单,我该如何做到这一点,而这正是它可以做到的方式。这可以应用于可能的现实世界场景。您的回答给出了两种方法都不能解决所描述的问题。这只是您的意见,在不了解实际用例的情况下应该以不同的方式完成。
猜你喜欢
  • 2011-05-12
  • 1970-01-01
  • 1970-01-01
  • 2012-09-23
  • 1970-01-01
  • 2016-08-10
  • 1970-01-01
  • 1970-01-01
  • 2012-09-22
相关资源
最近更新 更多