两个Java接口是否可以互斥?

13

我有两个界面,它们应该互斥:

interface Animal{}
interface Cat extends Animal{}
interface Bird extends Animal{}

如何防止实现同时实现 CatBird 接口的类?

class Impossible implements Cat, Bird{}

2
不确定这是否符合您的用例(我假设动物事物只是一个例子),但您不能将CatBird变成抽象类吗? - tobias_k
1
// Do not implement this interface together with Bird, because... - zch
3
尽管有几个答案的作者清楚地理解了这个问题,但这个问题被暂停了。此外,没有投票者要求任何澄清。我认为这个问题不应该被保持暂停状态。 - Torben
1
我同意@Torben的观点,并投票支持重新开放。对我来说,这个问题非常清晰明了。 - Duncan Jones
继承意味着某物是某物。接口意味着某物做某事。猫是动物。 - Neil McGuigan
显示剩余3条评论
8个回答

13

最后给出一个不太正规的解决方案:

public interface Animal<BEING extends Animal> {}
public interface Cat extends Animal<Cat> {}
public interface Bird extends Animal<Bird> {}
public class CatBird implements Cat, Bird {} // compiler error
public interface CatBird extends Cat, Bird {} // compiler error

CatBird无法实现,因为:

接口Animal不能使用不同的参数多次实现: Animal<Bird>与Animal<Cat>


1
我认为这一点都不糟糕。事实上,这是我找到的对这个问题最清晰、最顶尖的答案。泛型使用得很好。 - EpicPandaForce
1
是的。使接口不兼容是确保互斥性的正确方式。 - y o
@yo 真的吗?我担心自己的问题会得到很多赞。从“鸟”和“猫”是接口这个想法开始!让我问一下我的同事:“鸟”应该被设计成接口吗?他们会大喊“当然不!”如果“猫”和“鸟”是类,那么它们自然会被排除在外。但是,“不死者”和“凡人”等其他示例比“鸟”和“猫”更好地说明了接口的排除。嗷呜!(不是表达愤怒的嗷呜,而是表达像海盗一样说话的嗷呜) - Grim
从语义上讲,只要识别其实例不会改变控制流(因为这违反了关注点分离的原则,有点像类),将接口命名为类是没有问题的。由于你在问题中使用的名称并不涉及你实际进行接口操作的行为,所以很难知道你究竟想做什么。"implements Flying"与"implements Walking"并不是互斥的。鸟可以走路。 - y o
优秀的解决方案。这里的讨论很多都被困在了精确的例子 - 猫和鸟 - 上,但我的问题领域更加抽象,我有互斥的接口需要应用于一组类型。 - simon.watts
显示剩余2条评论

8

在这里,您有一个清晰的层次结构——一个根节点和几个分支,显然,一个节点(类、接口等)不能存在于多个分支中。为了强制执行这一点,应使用抽象类而不是接口。

当你的继承层次中有某些横切面可以被所有类共享,并且没有两个接口是互斥的时候,可以使用接口。

例如:

public abstract class Animal;
public interface CanFly;
public interface CanHunt;
public abstract class Cat extends Animal implements CanHunt;
public abstract class Bird extends Animal implements CanFly;
public class Vulture extends Bird implements CanHunt; //also CanFly because of Bird

至少还有一个人考虑过这个问题:http://instantbadger.blogspot.co.uk/2006/10/mutually-exclusive-interfaces.html


该链接提供了关于互斥接口的解决方案。

我没有对你的回答进行负评,但是“接口 != 抽象类”,所以我没有点赞。而且,“CanFly”是一种状态!我没有进行负评,因为我可以在任何地方抛出一个非法状态异常。 - Grim
我不会说 canFly 是一个状态。flying=trueflying=false 可能是一些有 canFly 能力的事物的可能状态,因此 canFly 可能指定方法 takeOff()land() - NickJ
这是学术术语!妥协方案:我们称其为可飞行,使用 setFlying(boolean) - Grim
那么解决方案是将用户询问的两个接口转换为抽象类? - IgorGanapolsky
嗯,这是“一个”解决方案。Java类可以实现多个接口,但不能扩展多个类。 - NickJ

3

有一个非常非常丑陋的解决方法。实现与Cat和Bird接口相冲突的方法签名:

public interface Cat {
    int x();
}

public interface Bird {
    float x();
}

/**
 * ERROR! Can not implement Cat and Bird because signatures for method x() differ!
 */
public class Impossible implements Cat, Bird {
}

但是不要这样做,相反应该想出更好的方法。

可能应该在Animal接口中:Animal getNaturalParent(); 这里我们所做的是一种丑陋的hack。我们不应该试图将其纳入实际设计中。最好将hack保留为一个完全无用的边缘方法,从未在任何其他地方实际使用。并在JavaDocs中记录hack存在的原因。 - Torben
3
此外,不允许猫鸟动物的存在将使得类似威尼斯狮子这样的神话生物失去资格。http://en.wikipedia.org/wiki/Lion_of_Venice - Torben
不兼容性是确保互斥性的唯一方式。你称其为丑陋,我则称之为优雅! - y o

3
Java中的接口是为了被实现而生的。只要一个类想要实现任何接口,它就可以这么做。因此,我认为没有办法阻止这种情况的发生。你可能需要重新考虑你的设计。

我对重新设计毫无头绪,请给个提示。 - Grim
1
正如其他人所展示的那样,有(好的和不好的)方法来防止它。 - Simon Forsberg

2

也许你无法完全避免这种情况。但是,你可以用一个抽象类替换CatBird,这样“不可能”只能继承一个。


1

不确定这是否有帮助,但如果您没有指定接口为公共接口,则该接口只能被定义在与接口相同的包中的类访问。

因此,您可以将接口放在一个包中,任何您不想实现它的类都可以放在另一个包中。


0
当一个类将要使用时,您可以抛出异常,UML图表如下所示:
enter image description here
如上所述,Possible将实现CatBird中的一个,但不会同时实现两者。但这只是一个图表,因此功能将是这样的。
interface Animal{void x();}
interface Cat extends Animal{}
interface Bird extends Animal{}
class Impossible implements Cat, Bird{

  @Override
  public void x() {
    System.out.println("Oops!");    
  }}

class Possible implements Cat{

  @Override
  public void x() {
    System.out.println("Blah blah");    
  }

}
class Core {
  private Animal a;
  public void setAnimal(Animal a) throws Exception {
    if (a instanceof Cat && a instanceof Bird) {
      System.out.println("Impossible!");
      throw new Exception("We do not accept magic");
    }
    this.a = a;
    a.x();
  }
  public static void main(String[] args) throws Exception {
    Core c = new Core();
    Possible p = new Possible();
    c.setAnimal(p);
    Impossible ip = new Impossible();
    c.setAnimal(ip);
  }
}

5
即使突变的鸟猫寿命不够长......这应该是编译时问题。 - Grim
对于一个“BirdCat”人来说,你会有另一个个体界面interface BirdCat implements Animal{} - user2511414
我同意@PeterRader的观点,用运行时检查来解决这个问题并不是一个好的解决方案。 - Duncan Jones
@DuncanJones 所以你可能需要实现自己的JRE。 - user2511414
@user2511414,你的评论太有建设性了:D - Grim
显示剩余3条评论

0

Java没有提供一种语法来防止一个类实现两个不同的接口。这是一件好事,因为接口应该让你忘记你正在处理哪个对象,只关注与该接口相关的功能。

在动物的情况下,这可能会令人感到困惑,因为在现实生活中,没有动物既是猫又是狗。但是,一个Java类可以履行Cat接口和Dog接口的契约,这没有任何问题。如果您想将其落实到现实中,请考虑一个包含猫和狗的盒子!

现在,正如Torben和其他人指出的那样,您可以故意将方法引入接口中,这些方法将与其他接口中的方法冲突。这将导致Java禁止在一个类中实现两个接口。但是,基于上述原因,这不是一个好的解决方法。

如果您必须强制执行此关系,则最好的方法是NickJ提供的答案


1
实际上,如果程序员定义了两个接口,他就可以防止这种情况发生。请看我的回答。 - Torben
@Torben Java并没有被设计成防止同时实现两个接口。当然,你已经发现了一个解决方法,但这并不改变Java并没有真正打算阻止你这样做的事实。因此,我认为我的答案仍然是正确的。 - Duncan Jones
这个问题不是关于Java是否被设计为允许它的。问题是它是否可能。尽管_事实_证明它是不正确的,但你仍在寻找答案的理由。 - Torben
@Torben 我已经编辑了我的问题以反映我想要表达的观点。我认为考虑Java作为一种语言自然允许的内容非常重要,因为每当你使用变通方法来对抗这种行为时,你往往会做错事情。你在自己的答案中也提到了这一点。 - Duncan Jones
1
这个答案是关于OOD最好的答案。让Java不具有多态性的决定绝对是史诗级的。实际上,我们失去了OOD的优势,因为仅仅有逻辑是不够的,我们需要一种编程中以前不需要的辩证法,即在func-rep-prog背后的门(重新激活钩子模式作为良好的实践)。 - Grim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接