Java类层次结构中的可选接口

3
假设您有一个Java类层次结构,大约有30个类,其中包括一个基类BaseClass和两个主要的子类(SubclassA,SubclassB),每个子类都有几个子类。其中一些子类具有某些特定行为。假设您可以“poke”它们,改变它们的状态。(这是对现有层次结构添加行为的新要求。“Poke”对大多数类来说毫无意义。)
interface Pokeable {
  void poke();
  int getTimesPoked();
}


public class Pokey extends SubclassB 
implements Pokeable {
  private int timesPoked = 0;
  public void poke() {
    timesPoked++;
  }
  public int getTimesPoked() {
    return timesPoked;
  }
}

这应该通过在仅需要它的类中实现 Pokeable,然后在必须 poke 任何可 poke 对象的所有代码中执行以下操作来完成吗?
public void process(BaseClass b) {
  if (b instanceof Pokeable) {
    ((Pokeable)b).poke();
  }
}

那么,是应该为了那些确实可 Pokeable 的少数情况而使整个层次结构实现 Pokeable 呢?

interface Pokeable {
  void poke();
  int getTimesPoked();
  boolean isReallyPokeable();
}

public class BaseClass implements Pokeable {
  public void poke() {}
  public int getTimesPoked() { return 0; }
  public boolean isReallyPokeable() { return false;}
}

public class Pokey {
  private int timesPoked = 0;
  @Override
  public void poke() {
    timesPoked++;
  }
  @Override
  public int getTimesPoked() {
    return timesPoked;
  }
  @Override
  public boolean isReallyPokeable() {
    return true;
  }
}

public void process(BaseClass b) {
  b.poke();
}

编辑添加:这有点像双重分派问题。当“扑克”代码对一个对象进行操作时,如果该对象是“可扑”,则必须调用“poke()”,否则不能调用。是否“poke()”取决于是否有东西想要poke和对象是否接受被poke。我可以使用访问者模式,但那似乎会使它更加复杂。

1
你的层次结构中没有一个层次可以决定下面的类是否可测试吗?这些类是否只需要在测试时可测试? - Morfic
多重继承并不能真正解决问题,因为你仍然需要对任何给定对象使用“instanceof”。 所以问题可能是:使用“instanceof”比让非Pokeable类实现Pokeable更好吗? - Mark Lutton
抱歉有这么多的“是的,但是…”的评论。如果有“消极徽章”的话,我可能会赢得它。但所有的答案都很有帮助,值得考虑。 - Mark Lutton
你能举一个这些对象被如何消耗的例子吗?根据您对我在答案评论中的回复,听起来消费者将尝试在可能的情况下poke()它们全部。您能澄清什么是poking以及在什么情况下执行它吗? - cheeken
这里有一个更直接的方法来达到我想要的效果:难道你不能简单地利用多态性并创建一个 process(Pokeable obj) 方法以及一个 process(BaseClass obj) 方法来变化处理类吗?process(Pokable obj) 可以在很大程度上委托给其他的 process 方法。 - cheeken
显示剩余4条评论
3个回答

4
如果一个类不适合被poke,那么我建议不要实现Pokeable接口,因为这样会让人感到困惑。可以采取以下方法之一:
1. 创建BaseClass的抽象或具体子类来实现Pokable,并让所有将被Pokeable的子类都继承它。如果在所有实现类中通过相同的逻辑来执行poke()操作,则此方法非常有效。
2. 让每个子类单独实现Pokeable。

比我好得多的答案。+1 - BlackVegetable
不幸的是,SubclassA / SubclassB 是一个维度;Pokeable / non-Pokeable 是另一维度。仅猜测数字,其中20个是Pokeable SubclassA,5个是non-Pokeable SubclassA,5个是Pokeable SubclassB,30个是non-Pokeable SubclassB。 - Mark Lutton
如何:BaseClass没有实现Pokeable,但是仍然有无用的poke()和getTimesPoked()方法。 Pokeable子类覆盖这两个方法。process(BaseClass b)可以调用b.poke()而不使用instanceof。真正可poke的子类实现了Pokeable;其他子类则没有。这似乎可以工作,但某些地方的风格似乎有些问题,因为“implements Pokeable”也可以是一个注释“// implements Pokeable”。 - Mark Lutton
@MarkLutton 为什么你不想在适用于各个类的类中实现 Pokeable 接口呢?是因为这感觉不太规则,还是因为这是很多工作,或者其他原因? - cheeken
只是因为在调用代码中需要“instanceof”。如果 (o instanceof Pokeable) { ((Pokeable)o).poke();} 如果看起来没问题,那我就这么做。 - Mark Lutton

2

我认为,如果一个方法被称为“isReallyX”,这是一个警示信号。如果有人一眼看到一个类被称为“可戳的(Pokeable)”,他们可能会尝试去戳它,但由于它并不是真正的可戳的,最终会导致意外行为。

我会采取你以前的方法,因为它不会误导外部程序员(或者在忽略这段代码两个月后,也不会误导你自己!)


一个 isReallyPokeable 方法是可疑的,但 instanceof 更加可疑。如果有类型 AB:AC:B,以及每个类型都有 Pokeable 版本,那么就没有单一类型体现了 Pokeable B 的概念,因为 Pokeable C 不继承自 Pokeable B,而是 Pokeable 并继承自 C(它不是 Pokeable 并继承自 B)。如果 A 包括 Poke 和 isReallyPokeable 方法,并且有一个 B 集合,不允许添加任何不是 Pokeable 的东西,那么可以直接在该集合中使用 Pokey 方法。 - supercat
我的观点是,现实世界中的问题并不总是与继承完全对齐;如果在编写类时知道某些派生类将实现某些功能,而其他类则不会,并且那些实现该功能的类不会共享一个公共基类,则让基类包括该功能的方法以及一种方法来说明它是否被支持比要求客户端代码使用instanceof检查和类型转换更“不邪恶”。 - supercat

0

如果您不想实现父类的所有方法,那么您的子类可以是抽象的。但是...

或者为了那些确实是可pokeable的少数人,整个层次结构都应该实现Pokeable吗?

绝对不行...不可pokeable的类不应该实现pokeable接口。

这应该通过仅在需要它的那些类中实现Pokeable来完成,然后在必须poke任何可pokeable对象的所有代码中执行以下操作吗?

这是一个稍微好一点的模型...但是,它是一个非常过程化的设计。

还可以将对象的集合保留在某个地方,然后快速迭代该集合以poke它们全部吗?

您能让可pokeable的类向事件调度程序注册吗?...因此,当发生某个特定事件时,所有类都会被poke?


实际上不是这样的。在真正的应用中,当SubclassA不必是其自己的子类之一时,我们会实例化它。 - Mark Lutton
“事件分发器”:这比那要复杂一些。进行“戳”的是对特定对象执行某些操作的东西,因此一次只能对一个对象进行“戳”。 - Mark Lutton

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