是否可以避免在此示例中使用类型检查?

时间:2009-11-02 01:16:45

标签: java interface typechecking

对不起这个糟糕的头衔,想不出一个简洁的方式把这个......

我正在考虑拥有一个全部属于特定界面的对象列表。然后,这些对象中的每一个都可以实现其他接口,但是不能保证哪个对象将实现哪个。但是,在单个循环中,我希望能够调用其进一步子类型的方法。

即3个接口:

public interface IAnimal { ... }
public interface IEggLayer { public Egg layEgg(); }
public interface IMammal { public void sweat(); }

然后将其存储为

private List<IAnimal> animals= new ArrayList<IAnimal>();

因此,添加到列表中的实例也可能是IEggLayerIMammal类型,它们具有完全不相关的方法。

我最初的本能是做

for(IAnimal animal : animals) {
  if(animal instanceof IEggLayer) {
    egg = ((IEggLayer)animal).layEgg();
  }
  if(animal instance of IMammal) {
    ((IMammal)animal).sweat();
  }
}

但我总是被告知,类型检查表明代码应该被重构。

由于单个对象可能同时执行[platypus],意味着单个doFunction()在这里不合适,是否可以避免在这种情况下使用类型检查,或者这是一个类型检查被归类为可接受的实例吗? 是否有可能迎合这种设计模式?

我为这个人为的例子道歉...... [请忽略任何语法错误 - 它只是用于类似Java的伪代码]

我在EggLayer的使用中添加了左值,以表明有时返回类型很重要

6 个答案:

答案 0 :(得分:5)

显然,您的IAnimal接口(或其某些扩展名)需要callAllMethods方法,接口的每个实现者都可以编码以多态方式执行此任务 - 似乎是唯一的OO声音方法!

答案 1 :(得分:1)

在C#中,您应该能够透明地执行此操作。

foreach(IEggLayer egglayer in animals) {
    egglayer.layEgg();
}

foreach(IMammal mammal in animals) {
    mammal.sweat();
}

答案 2 :(得分:1)

我认为考虑这个问题的方法是:循环在做什么?循环有一个目的,并试图对这些对象做一些事情。这个东西可以在IAnimal接口上有一个方法,并且实现可以根据需要出汗或产卵。

就你的返回值问题而言,你将返回null,如果你共享这些方法,你无能为力。在循环中抛出是不值得的,以避免额外的返回null;满足编译器。但是,您可以使用泛型使其更明确:

  public interface IAnimal<R> {

         public R generalMethod();

  }

  public interface IEggLayer extends IAnimal<Egg> {

         public Egg generalMethod(); //not necessary, but the point is it works.

  }

  public interface IMammal extends IAnimal<Void> {

        public Void generalMethod();

  }

从您关注返回类型的注释中,您可以获取返回类型并将其发送到工厂方法,该方法检查类型并返回一些通用的子类型,并将其作用于特定类型并对其进行操作。

答案 3 :(得分:1)

  

但我总是被告知,类型检查表明代码应该被重构。

这表示类层次结构或使用它的代码可能需要重构或重构。但通常不会有重构/重组来避免这个问题。

在这种情况下,如果您的方法仅适用于特定的子类型,那么最有希望的重构就是为动物蛋层和出汗的动物分别制作列表。

但如果你不能这样做,你需要做一些类型检查。即使isEggLayer() / isMammal()涉及类型检查;例如

if (x.isEggLayer()) {
    ((IEggLayer) x).layEgg();  // type cast is required.
}

我想您可以通过asEggLayer()方法隐藏类型检查; e.g。

public IEggLayer asEggLayer() {
    return ((IEggLayer) this);
}

// Not recommended ...
public IEggLayer asEggLayer() {
    return (this instanceof IEggLayer) ? ((IEggLayer) this) : null;
}

但是总会发生一次类型检查,以及它会失败的可能性。此外,所有这些隐藏类型检查的尝试都需要将子类型的“知识”添加到超类型接口,这意味着需要在添加新的子类型时对其进行更改。

答案 4 :(得分:0)

为什么不将方法添加到isAnimal:

public interface IAnimal {
   bool isEggLayer();
   bool isMammal();
}

然后你可以遍历并且每次只查询这个布尔值。

<强>更新 如果这是绘制动物,那么拥有一个完全封闭的类是合理的,你只需要调用drawVehicle并绘制一个护卫舰,赛斯纳,摩托车等等。

但是,这似乎有一个非OOP架构,所以如果应用程序的架构不能改变那么,因为我的原始答案没有得到很好的接受,那么AOP似乎是最好的选择。

如果您在每个课程上添加注释,则可以

@IsEggLayer
@IsMammal
public class Platypus() implements EggLayer, Mammal {
...
}

这将使您能够创建拉出所有egglayers并执行任何操作的方面。

您还可以向动物界面注入任何其他类,以使这个想法发挥作用。

我需要考虑从这里开始,但我觉得如果不能重新设计,这可能是最好的解决方案。

答案 5 :(得分:0)

有很多方法可以解决这个问题。最合适的Exaclty取决于您的背景。我建议引入一个额外的间接层。这解决了大多数问题。我更喜欢避免接口多重继承的设计(如果我是一种语言的主席,我会禁止它)。

我认为layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant()不是用Animal来表达Animal的好方法。因此,不要将每个animals直接添加到private final List<AnimalWrapper> animals = new ArrayList<AnimalWrapper>(); public void doStuff() { for (AnimalWrapper animal : animals) { animal.doStuff(); } } ,而是将每个public void addPlatypus(final Platypus platypus) { animals.add(new AnimalWrapper() { public void doYourStuff() { platypus.sweat(); platypus.layEgg(); }}); } 包装在一个单独的包装器中。我说“包装器”是一个通用名称 - 我们试图执行的随机操作没有任何意义。

/*** Poor context -> trouble ***/

public void addNormalMamal(final Mamal mamal) {
    animals.add(new AnimalWrapper() { public void doYourStuff() {
        mamal.sweat();
    }});
}
public void addNormalEggLayer(final EggLayer eggLayer) {
    animals.add(new AnimalWrapper() { public void doYourStuff() {
        eggLayer.layEgg();
    }});
}
public <T extends Mamal & EggLayer> void addMamalEggLayer(final T animal) {
    animals.add(new AnimalWrapper() { public void doYourStuff() {
        animal.sweat();
        animal.layEgg();
    }});
}

然后我们需要一些添加包装器的方法。类似的东西:

{{1}}

如果您尝试在没有足够上下文的情况下编写这些包装器,则会遇到麻烦。这些要求在呼叫站点选择正确的一个。它可以通过重载来完成,但这有危险。

{{1}}