对不起这个糟糕的头衔,想不出一个简洁的方式把这个......
我正在考虑拥有一个全部属于特定界面的对象列表。然后,这些对象中的每一个都可以实现其他接口,但是不能保证哪个对象将实现哪个。但是,在单个循环中,我希望能够调用其进一步子类型的方法。
即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();
}
}
但我总是被告知,类型检查表明代码应该被重构。
由于单个对象可能同时执行[platypus],意味着单个doFunction()
在这里不合适,是否可以避免在这种情况下使用类型检查,或者这是一个类型检查被归类为可接受的实例吗?
是否有可能迎合这种设计模式?
我为这个人为的例子道歉...... [请忽略任何语法错误 - 它只是用于类似Java的伪代码]
我在EggLayer的使用中添加了左值,以表明有时返回类型很重要
答案 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}}