java—在本例中是否可以避免使用类型检查?

egdjgwm8  于 2021-07-05  发布在  Java
关注(0)|答案(6)|浏览(275)

很抱歉标题不好,想不出一个简洁的方式来表达这个。。
我想有一个对象列表,所有对象都是一个特定的接口。然后,这些对象中的每一个都可以实现进一步的接口,但是不能保证哪个对象将实现哪个接口。但是,在一个循环中,我希望能够调用任何子类型的方法。
ie,3个接口:

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

然后将其存储为

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

因此,添加到列表中的示例也可能是 IEggLayer 或者 IMammal ,它们有完全不相关的方法。
我最初的直觉是这样做

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

但我一直被告知类型检查是代码真正应该重构的标志。
因为一个物体可以同时做这两件事(例如鸭嘴兽),这意味着 doFunction() 在这里不合适,在这种情况下是否可以避免使用类型检查,或者这是一个类型检查被归类为可接受的示例?
有没有一种设计模式可以满足这种需求?
我也为这个做作的例子道歉。。。
[请忽略任何语法错误-它只用于类似java的伪代码]
我在egglayer的用法中添加了lvalue,以表明有时返回类型很重要

2lpgd968

2lpgd9681#

有很多方法可以做到这一点。最恰当的解释取决于你的上下文。我建议引入一个附加的间接层。这解决了大多数问题。我更喜欢避免接口多重继承的设计(如果我是一种语言的主席,我会禁止它)。
我不认为 layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant() 是辩论的好方法 Animal 与。所以不是每个都加上 Animal 直接到 animals ,用单独的 Package 纸 Package 。我说“wrapper”是一个通用名称——我们尝试执行的随机操作没有任何意义。

private final List<AnimalWrapper> animals =
    new ArrayList<AnimalWrapper>();

public void doStuff() {
    for (AnimalWrapper animal : animals) {
        animal.doStuff();
    }
}

然后我们需要一些添加 Package 的方法。比如:

public void addPlatypus(final Platypus platypus) {
    animals.add(new AnimalWrapper() { public void doYourStuff() {
        platypus.sweat();
        platypus.layEgg();
    }});
}

如果你试图在没有足够上下文的情况下编写这些 Package 器,你会遇到麻烦。这些要求在呼叫站点选择正确的一个。它可以通过超载来实现,但这是有危险的。

/***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();
    }});
}
r1zk6ea1

r1zk6ea12#

显然你的 IAnimal 接口(或其某些扩展)需要 callAllMethods 接口的每个实现者都可以编写代码以多态方式执行此任务的方法——似乎是唯一一种面向对象的方法!

qkf9rpyu

qkf9rpyu3#

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

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

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

xwbd5t1u4#

我认为思考这个问题的方法是:循环在做什么?循环是有目的的,它试图对这些对象做些什么。有些东西可以在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();

  }

从您关心返回类型的注解中,您可以获取返回类型并将其分派给工厂方法,工厂方法检查该类型并返回一些泛型,这些泛型被细分到特定类型并对其执行操作。

8dtrkrch

8dtrkrch5#

为什么不在isanimal中添加方法:

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

然后您可以循环并每次只查询这个布尔值。
更新:如果这是画一个动物,那么拥有一个完全封闭的类是合理的,你只要调用 drawVehicle 它画了一辆克尔维特,塞斯纳,摩托车,随便什么。
但是,这似乎是一个非面向对象的体系结构,所以如果应用程序的体系结构不能改变,因为我最初的答案不是很受欢迎,那么aop似乎是最好的选择。
如果在每个类上添加注解,则可以

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

然后,这将使您能够创建将所有egglayer拉出的方面,并执行任何需要执行的操作。
您还可以向动物接口中注入任何其他类来实现这个想法。
我将需要考虑从这里去哪里,但我有一种感觉,这可能是最好的解决方案,如果不能重新设计。

gwbalxhn

gwbalxhn6#

但我一直被告知类型检查是代码真正应该重构的标志。
这表明类层次结构或使用它的代码可能需要重构或重构。但通常不会有重构/重组来避免问题。
在这种情况下,如果您有只适用于特定子类型的方法,那么最有希望的重构将是为卵子层的动物和出汗的动物建立单独的列表。
但如果你不能做到这一点,你将需要做一些类型检查。即使是 isEggLayer() / isMammal() 涉及类型检查;例如

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

我想你可以通过一个 asEggLayer() 方法;例如

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

或者

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

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

相关问题