简体   繁体   English

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

[英]Is it possible to avoid using type checking in this example?

Sorry for the poor title, can't think of a succinct way of putting this.. 对不起这个糟糕的头衔,想不出一个简洁的方式把这个..

I'm thinking of having a list of objects that will all be of a specific interface. 我正在考虑拥有一个对象列表,这些对象都是特定的接口。 Each of these objects may then implement further interfaces, but there is no guarantee which object will implement which. 然后,这些对象中的每一个都可以实现其他接口,但是不能保证哪个对象将实现哪个。 However, in a single loop, I wish to be able to call the methods of whatever their further sub-type may be. 但是,在单个循环中,我希望能够调用其进一步子类型的方法。

Ie, 3 interfaces: 即3个接口:

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

this would then be stored as 然后将其存储为

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

so, instances added to the list could possibly also be of type IEggLayer or IMammal , which have completely unrelated methods. 因此,添加到列表中的实例也可能是IEggLayerIMammal类型,它们具有完全不相关的方法。

My initial instinct would be to then do 我最初的本能就是这样做

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

But I have always been told that type checking is a sign that the code should really be refactored. 但我总是被告知类型检查是代码应该真正被重构的标志。

Since it could be possible for a single object to do both [platypus, for example], meaning that a single doFunction() would not be suitable here, is it possible to avoid using type checking in this case, or is this an instance where type checking is classed as acceptable? 由于单个对象可能同时执行[platypus],这意味着单个doFunction()在这里不合适,是否可以避免在这种情况下使用类型检查,或者这是一个实例型式检查是否可以接受?
Is there possibly a design pattern catered to this? 是否有可能迎合这种设计模式?

I apologise for the contrived example as well... 我也为这个人为的例子道歉......
[Ignore any syntax errors, please - it's only intended to be Java-like pseudocode] [请忽略任何语法错误 - 它只是用于类似Java的伪代码]

I've added lvalue to the EggLayer use, to show that sometimes the return type is important 我已经为EggLayer使用添加了左值,以表明有时返回类型很重要

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

in C#, you should be able to do this transparently. 在C#中,您应该能够透明地执行此操作。

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

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

I think the way to think about this question is: What is the loop doing? 我认为考虑这个问题的方法是:循环在做什么? The loop has a purpose and is trying to do something with those objects. 循环有一个目的,并试图对这些对象做一些事情。 That something can have a method on the IAnimal interface, and the implementations can sweat or lay eggs as needed. 这个东西可以在IAnimal接口上有一个方法,并且实现可以根据需要出汗或产卵。

In terms of your issue with the return value, you will be returning null, nothing you can do about that if you share the methods. 就你的返回值问题而言,你将返回null,如果你共享这些方法,你无能为力。 It is not worth casting within a loop to avoid an extra return null; 在循环中抛出是不值得的,以避免额外的返回null; to satisfy the compiler. 满足编译器。 You can, however, make it more explicit using generics: 但是,您可以使用泛型使其更明确:

  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();

  }

From your comment where you care about the return type, you can get the return type and dispatch it to a factory method which examines the type and returns something generic that is sublcassed to the specific type and act on that. 从您关注返回类型的注释中,您可以获取返回类型并将其分派给工厂方法,该方法检查类型并返回一些通用的子类型,并将其作用于特定类型并对其进行操作。

But I have always been told that type checking is a sign that the code should really be refactored. 但我总是被告知类型检查是代码应该真正被重构的标志。

It is a sign that either class hierarchy or the code that uses it may need to be refactored or restructured. 这表示类层次结构或使用它的代码可能需要重构或重构。 But often there will be no refactoring / restructuring that avoids the problem. 但通常不会有重构/重组来避免这个问题。

In this case, where you have methods that apply only to specific subtypes, the most promising refactor would be to have separate lists for the animals that are egg layers and the animals that sweat. 在这种情况下,如果您的方法仅适用于特定的子类型,那么最有希望的重构就是为鸡蛋层和动物出汗的动物分别制作列表。

But if you cannot do that, you will need to do some type checking. 但如果你不能这样做,你需要做一些类型检查。 Even the isEggLayer() / isMammal() involves a type check; 甚至isEggLayer() / isMammal()涉及类型检查; eg 例如

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

I suppose that you could hide the type check via an asEggLayer() method; 我想你可以通过asEggLayer()方法隐藏类型检查; eg 例如

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

or 要么

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

But there is always a typecheck happening, and the possibility that it will fail. 但是总会发生一次类型检查,并且它可能会失败。 Furthermore, all of these attempts to hide the type checking entail adding "knowledge" of the subtypes to the supertype interface, which means that it needs to be changed as new subtypes are added. 此外,所有这些隐藏类型检查的尝试都需要将子类型的“知识”添加到超类型接口,这意味着它需要在添加新的子类型时进行更改。

Why not have methods added to isAnimal: 为什么不将方法添加到isAnimal:

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

Then you can loop through and just query this boolean each time. 然后你可以遍历并且每次只查询这个布尔值。

Update: If this was drawing an animal, then having a class that is completely enclosed is reasonable, you just call drawVehicle and it draws a corvette, cessna, motorcycle, whatever. 更新:如果这是绘制动物,那么拥有一个完全封闭的类是合理的,你只需要调用drawVehicle并绘制一个护卫舰,赛斯纳,摩托车等等。

But, this seems to have a non-OOP architecture, so if the architecture of the application can't change then, since my original answer isn't well received, then it would seem that AOP would be the best choice. 但是,这似乎有一个非OOP架构,所以如果应用程序的架构不能改变那么,因为我的原始答案没有得到很好的接受,那么AOP似乎是最好的选择。

If you put an annotation on each class, you can have 如果你在每个类上添加一个注释,你就可以拥有

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

This would then enable you to create aspects that pull out all the egglayers and do whatever operations need to be done. 这将使您能够创建拉出所有egglayers并执行任何需要执行的操作的方面。

You can also inject into the animal interfaces any additional classes to get this idea to work. 您还可以向动物界面注入任何其他类以使这个想法发挥作用。

I will need to think about where to go from here, but I have a feeling this may be the best solution, if a redesign can't be done. 我需要考虑从这里开始,但我觉得这可能是最好的解决方案,如果不能进行重新设计。

There are many ways of going about this. 有很多方法可以解决这个问题。 Exaclty which is most appropriate depends upon your context. 最合适的Exaclty取决于您的背景。 I am going to suggest introducing an additional layer of indirection. 我建议引入一个额外的间接层。 This solves most problems. 这解决了大多数问题。 I prefer designs which avoid multiple inheritance of interface (if I were president of a language, I would forbid it). 我更喜欢避免多重继承接口的设计(如果我是一种语言的总裁,我会禁止它)。

I don't think layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant() is a great method to polute Animal with. 我不认为layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant()是一个很好的方法来判断Animal So instead of adding each Animal directly to animals , wrap each in an individual wrapper. 因此,不是将每个Animal直接添加到animals ,而是将每个Animal包裹在一个单独的包装中 I say "wrapper" as a generic name - the random operation we are trying to perform doesn't make any sense. 我说“包装器”作为通用名称 - 我们试图执行的随机操作没有任何意义。

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

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

Then we need some way of adding the wrappers. 然后我们需要一些添加包装器的方法。 Something like: 就像是:

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

If you try to write these wrappers without enough context you get into trouble. 如果你尝试在没有足够上下文的情况下编写这些包装器,你就会遇到麻烦 These require that the correct one is selected at call site. 这些要求在呼叫站点选择正确的一个。 It could be done by overloading, but that has dangers. 它可以通过重载完成,但这有危险。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM